การจดจำผู้ใช้โดยไม่ต้องใช้คุกกี้หรือที่เก็บข้อมูลในเครื่อง


133

ฉันกำลังสร้างเครื่องมือวิเคราะห์และตอนนี้ฉันสามารถรับที่อยู่ IP ของผู้ใช้เบราว์เซอร์และระบบปฏิบัติการจากตัวแทนผู้ใช้ของพวกเขาได้

ฉันสงสัยว่ามีความเป็นไปได้ที่จะตรวจพบผู้ใช้คนเดียวกันโดยไม่ใช้คุกกี้หรือที่เก็บข้อมูลในเครื่องหรือไม่? ฉันไม่ได้คาดหวังตัวอย่างโค้ดที่นี่ เพียงคำใบ้ง่ายๆว่าจะดูที่ไหนเพิ่มเติม

ลืมบอกไปว่าต้องใช้งานข้ามเบราว์เซอร์ได้ถ้าเป็นคอมพิวเตอร์ / อุปกรณ์เครื่องเดียวกัน โดยทั่วไปแล้วฉันหลังจากการจดจำอุปกรณ์ไม่ใช่ผู้ใช้จริงๆ


5
ไม่จริง - อย่างน้อยก็ไม่มีวิธีใดที่คุณสามารถวางใจได้ว่าถูกต้อง อาจเป็นแฮชของทั้งสามอย่างรวมกันอย่างไรก็ตามหากมีคนมากกว่าหนึ่งคนในบ้านที่ใช้เบราว์เซอร์และระบบปฏิบัติการเดียวกันก็ยังใช้ไม่ได้ นอกจากนี้ ISP ส่วนใหญ่ยังให้ที่อยู่ IP แบบไดนามิกซึ่งหมายความว่าจะมีการเปลี่ยนแปลงทุก ๆ ครั้งและจะไม่สามารถพึ่งพาเพื่อวัตถุประสงค์ในการระบุตัวตนได้
จอน

2
แล้วคุณจะไม่รู้ว่าเซสชันคืออะไร กรณีการใช้งานของคุณคือสิ่งที่เซสชันออกแบบมาสำหรับ เซสชันไม่เกี่ยวข้องกับการเข้าสู่ระบบหรือการรับรองความถูกต้อง เว็บเซิร์ฟเวอร์ของคุณจะแจ้งให้ลูกค้าส่งคุกกี้พร้อมตัวระบุเซสชัน คุณระบุลูกค้ารายนั้นโดยใช้รหัสเซสชันที่พวกเขาส่งให้คุณ
Man Vs Code

4
คุกกี้ยังคงใช้งานได้? เหตุใดคุณจึงหลีกเลี่ยงการใช้คุกกี้
Baba

2
มันง่ายมากและฉันใช้มันตลอดเวลาขอให้ผู้ใช้ป้อนชื่อผู้ใช้และรหัสผ่าน !!!
Amit Kriplani

2
นี่คือวิธีแก้ปัญหา javascript ขั้นต่ำ (ไม่ใช่ข้ามเบราว์เซอร์ในกรณีนี้): github.com/carlo/jquery-browser-fingerprint ที่ฉันพูดถึงเพราะมันทำให้ฉันเข้าใจว่าปลั๊กอินจำนวนมากติดตั้งข้ามเบราว์เซอร์โดยค่าเริ่มต้นโดยไม่มี ทางเลือกใด ๆ ในส่วนของผู้ใช้ การจัดเรียงสิ่งเหล่านี้อย่างรอบคอบ (ซึ่งไม่ใช่งานเล็ก ๆ แต่ก็ยัง ... ) อาจนำไปสู่คุณสมบัติที่ไม่เชื่อเรื่องพระเจ้าของเบราว์เซอร์ที่จับต้องได้ของลายนิ้วมือบนอุปกรณ์ขนาดใหญ่
hexalys

คำตอบ:


396

บทนำ

ถ้าฉันเข้าใจคุณถูกต้องคุณต้องระบุผู้ใช้ที่คุณไม่มีรหัสประจำตัวดังนั้นคุณจึงต้องการทราบว่าพวกเขาเป็นใครโดยการจับคู่ข้อมูลแบบสุ่ม คุณไม่สามารถจัดเก็บข้อมูลประจำตัวของผู้ใช้ได้อย่างน่าเชื่อถือเนื่องจาก:

  • สามารถลบคุกกี้ได้
  • ที่อยู่ IP สามารถเปลี่ยนแปลงได้
  • เบราว์เซอร์สามารถเปลี่ยนแปลงได้
  • แคชเบราว์เซอร์อาจถูกลบ

Java Applet หรือ Com Object น่าจะเป็นวิธีแก้ปัญหาที่ง่ายโดยใช้แฮชข้อมูลฮาร์ดแวร์ แต่ทุกวันนี้ผู้คนตระหนักถึงความปลอดภัยมากว่าจะยากที่จะให้ผู้ใช้ติดตั้งโปรแกรมประเภทนี้ในระบบของตน สิ่งนี้ทำให้คุณติดอยู่กับการใช้คุกกี้และเครื่องมืออื่น ๆ ที่คล้ายคลึงกัน

คุกกี้และเครื่องมืออื่น ๆ ที่คล้ายคลึงกัน

คุณอาจพิจารณาการสร้างข้อมูลโปรไฟล์แล้วใช้การทดสอบความน่าจะเป็นในการระบุผู้ใช้น่าจะเป็น โปรไฟล์ที่มีประโยชน์สำหรับสิ่งนี้สามารถสร้างขึ้นได้จากการรวมกันของสิ่งต่อไปนี้:

  1. ที่อยู่ IP
    • ที่อยู่ IP จริง
    • ที่อยู่ IP ของพร็อกซี (ผู้ใช้มักใช้พร็อกซีเดิมซ้ำ ๆ )
  2. คุ้กกี้
  3. ข้อบกพร่องของเว็บ (เชื่อถือได้น้อยลงเนื่องจากข้อบกพร่องได้รับการแก้ไข แต่ก็ยังมีประโยชน์)
    • PDF Bug
    • แฟลชบั๊ก
    • Java Bug
  4. เบราว์เซอร์
    • การติดตามการคลิก (ผู้ใช้จำนวนมากเข้าชมชุดหน้าเดียวกันในการเข้าชมแต่ละครั้ง)
    • เบราว์เซอร์ Finger Print - ปลั๊กอินที่ติดตั้ง (ผู้คนมักจะมีชุดปลั๊กอินที่แตกต่างกันและไม่ซ้ำกัน)
    • รูปภาพที่แคช (บางครั้งผู้ใช้จะลบคุกกี้ของพวกเขา แต่ปล่อยภาพแคชไว้)
    • ใช้ Blobs
    • URL (ประวัติเบราว์เซอร์หรือคุกกี้อาจมี ID ผู้ใช้ที่ไม่ซ้ำกันใน URL เช่นhttps://stackoverflow.com/users/1226894หรือhttp://www.facebook.com/barackobama?fref=ts )
    • การตรวจหาแบบอักษรของระบบ (นี่คือลายเซ็นคีย์ที่ไม่ค่อยมีใครรู้จัก แต่มักไม่ซ้ำกัน)
  5. HTML5 และ Javascript
    • HTML5 LocalStorage
    • HTML5 Geolocation API และ Reverse Geocoding
    • สถาปัตยกรรม, ภาษาของระบบปฏิบัติการ, เวลาของระบบ, ความละเอียดหน้าจอ ฯลฯ
    • API ข้อมูลเครือข่าย
    • API สถานะแบตเตอรี่

แน่นอนว่ารายการที่ฉันระบุไว้นั้นเป็นเพียงไม่กี่วิธีที่เป็นไปได้ที่ผู้ใช้สามารถระบุได้โดยไม่ซ้ำกัน ยังมีอีกมากมาย

ด้วยองค์ประกอบข้อมูลสุ่มชุดนี้ในการสร้างโปรไฟล์ข้อมูลมีอะไรต่อไป

ขั้นตอนต่อไปคือการพัฒนาFuzzy Logicหรือที่ดีกว่านั้นคือArtificial Neural Network (ซึ่งใช้ตรรกะคลุมเครือ) ไม่ว่าในกรณีใดแนวคิดคือการฝึกอบรมระบบของคุณจากนั้นรวมการฝึกอบรมกับการอนุมานแบบเบย์เพื่อเพิ่มความแม่นยำของผลลัพธ์ของคุณ

โครงข่ายประสาทเทียม

NeuralMeshห้องสมุดสำหรับ PHP ช่วยให้คุณสามารถสร้างโครงข่ายประสาทเทียม หากต้องการใช้ Bayesian Inference โปรดดูลิงก์ต่อไปนี้:

ณ จุดนี้คุณอาจคิดว่า:

ทำไมคณิตศาสตร์และลอจิกมากมายสำหรับงานที่ดูเหมือนง่าย?

โดยทั่วไปเพราะมันเป็นไม่ได้เป็นงานที่ง่าย สิ่งที่คุณพยายามจะบรรลุคือความน่าจะเป็นที่แท้จริง ตัวอย่างเช่นให้ผู้ใช้ที่รู้จักดังต่อไปนี้:

User1 = A + B + C + D + G + K
User2 = C + D + I + J + K + F

เมื่อคุณได้รับข้อมูลต่อไปนี้:

B + C + E + G + F + K

คำถามที่คุณถามเป็นหลักคือ:

ความน่าจะเป็นที่ข้อมูลที่ได้รับ (B + C + E + G + F + K) เป็น User1 หรือ User2 เป็นอย่างไร และสองแมตช์ใดที่น่าจะเป็นไปได้มากที่สุด ?

เพื่อให้ตอบคำถามนี้ได้อย่างมีประสิทธิภาพคุณต้องเข้าใจความถี่เทียบกับรูปแบบความน่าจะเป็นและสาเหตุที่ความน่าจะเป็นร่วมอาจเป็นแนวทางที่ดีกว่า รายละเอียดมีมากเกินไปที่จะเข้ามาที่นี่ (ซึ่งเป็นสาเหตุที่ฉันให้ลิงก์แก่คุณ) แต่ตัวอย่างที่ดีคือแอปพลิเคชันตัวช่วยสร้างการวินิจฉัยทางการแพทย์ซึ่งใช้การรวมกันของอาการเพื่อระบุโรคที่เป็นไปได้

คิดสักครู่ของชุดของจุดข้อมูลซึ่งประกอบด้วยข้อมูลโปรไฟล์ของคุณ (B + C + E + G + F + K ในตัวอย่างข้างต้น) เป็นอาการและไม่ทราบผู้ใช้งานเป็นโรค ด้วยการระบุโรคคุณสามารถระบุวิธีการรักษาที่เหมาะสมเพิ่มเติมได้ (ถือว่าผู้ใช้รายนี้เป็น User1)

เห็นได้ชัดว่าโรคที่เราระบุได้มากกว่า 1 อาการนั้นง่ายต่อการระบุ ในความเป็นจริงยิ่งเราสามารถระบุอาการได้มากขึ้นการวินิจฉัยของเราก็จะง่ายและแม่นยำมากขึ้นเท่านั้น

มีทางเลือกอื่นหรือไม่?

แน่นอน. อีกวิธีหนึ่งคือการวัดผลคุณอาจสร้างอัลกอริทึมการให้คะแนนอย่างง่ายของคุณเองและอิงตามการจับคู่ วิธีนี้ไม่มีประสิทธิภาพเท่ากับความน่าจะเป็น แต่อาจจะง่ายกว่าสำหรับคุณในการนำไปใช้

ตัวอย่างเช่นพิจารณาแผนภูมิคะแนนง่ายๆนี้:

+ ------------------------- + -------- + ------------ +
| คุณสมบัติ | น้ำหนัก | ความสำคัญ |
+ ------------------------- + -------- + ------------ +
| ที่อยู่ IP จริง | 60 | 5 |
| ที่อยู่ IP ของพร็อกซีที่ใช้ | 40 | 4 |
| คุกกี้ HTTP | 80 | 8 |
| คุกกี้เซสชัน | 80 | 6 |
| คุกกี้ของบุคคลที่สาม | 60 | 4 |
| คุกกี้แฟลช | 90 | 7 |
| PDF Bug | 20 | 1 |
| แฟลชบั๊ก | 20 | 1 |
| Java Bug | 20 | 1 |
| หน้าเว็บบ่อย | 40 | 1 |
| เบราว์เซอร์ลายนิ้วมือ | 35 | 2 |
| ปลั๊กอินที่ติดตั้ง | 25 | 1 |
| รูปภาพแคช | 40 | 3 |
| URL | 60 | 4 |
| การตรวจหาแบบอักษรของระบบ | 70 | 4 |
| Localstorage | 90 | 8 |
| ตำแหน่งทางภูมิศาสตร์ | 70 | 6 |
| AOLTR | 70 | 4 |
| API ข้อมูลเครือข่าย | 40 | 3 |
| API สถานะแบตเตอรี่ | 20 | 1 |
+ ------------------------- + -------- + ------------ +

สำหรับข้อมูลแต่ละชิ้นที่คุณสามารถรวบรวมตามคำขอที่กำหนดให้ให้คะแนนที่เกี่ยวข้องจากนั้นใช้ความสำคัญเพื่อแก้ไขความขัดแย้งเมื่อคะแนนเท่ากัน

หลักฐานแนวคิด

สำหรับหลักฐานที่เรียบง่ายของแนวคิดโปรดดูที่Perceptron Perceptron เป็นRNA Modelที่ใช้โดยทั่วไปในแอพพลิเคชั่นจดจำรูปแบบ แม้จะมีคลาส PHPเก่าที่ใช้งานได้อย่างสมบูรณ์แบบ แต่คุณอาจต้องปรับเปลี่ยนตามวัตถุประสงค์ของคุณ

แม้จะเป็นเครื่องมือที่ยอดเยี่ยม แต่ Perceptron ยังคงสามารถส่งคืนผลลัพธ์ได้หลายรายการ (การจับคู่ที่เป็นไปได้) ดังนั้นการใช้การเปรียบเทียบคะแนนและความแตกต่างยังคงมีประโยชน์ในการระบุสิ่งที่ดีที่สุดของการแข่งขันเหล่านั้น

สมมติฐาน

  • จัดเก็บข้อมูลที่เป็นไปได้ทั้งหมดเกี่ยวกับผู้ใช้แต่ละคน (IP คุกกี้ ฯลฯ )
  • ในกรณีที่ผลการแข่งขันเป็นแบบตรงทั้งหมดให้เพิ่มคะแนนขึ้น 1
  • ในกรณีที่ผลลัพธ์ไม่ใช่การแข่งขันแบบตรงทั้งหมดให้ลดคะแนนลง 1

ความคาดหวัง

  1. สร้างฉลาก RNA
  2. สร้างผู้ใช้แบบสุ่มจำลองฐานข้อมูล
  3. สร้างผู้ใช้ที่ไม่รู้จักคนเดียว
  4. สร้าง RNA และค่าของผู้ใช้ที่ไม่รู้จัก
  5. ระบบจะผสานข้อมูล RNA และสอน Perceptron
  6. หลังจากฝึก Perceptron แล้วระบบจะมีชุดถ่วงน้ำหนัก
  7. ตอนนี้คุณสามารถทดสอบรูปแบบของผู้ใช้ที่ไม่รู้จักและ Perceptron จะสร้างชุดผลลัพธ์
  8. จัดเก็บการแข่งขันเชิงบวกทั้งหมด
  9. เรียงลำดับการแข่งขันก่อนตามคะแนนจากนั้นตามความแตกต่าง (ตามที่อธิบายไว้ข้างต้น)
  10. แสดงผลลัพธ์ที่ใกล้เคียงที่สุดสองรายการหรือหากไม่พบรายการที่ตรงกันให้แสดงผลลัพธ์ที่ว่างเปล่า

รหัสสำหรับการพิสูจน์แนวคิด

$features = array(
    'Real IP address' => .5,
    'Used proxy IP address' => .4,
    'HTTP Cookies' => .9,
    'Session Cookies' => .6,
    '3rd Party Cookies' => .6,
    'Flash Cookies' => .7,
    'PDF Bug' => .2,
    'Flash Bug' => .2,
    'Java Bug' => .2,
    'Frequent Pages' => .3,
    'Browsers Finger Print' => .3,
    'Installed Plugins' => .2,
    'URL' => .5,
    'Cached PNG' => .4,
    'System Fonts Detection' => .6,
    'Localstorage' => .8,
    'Geolocation' => .6,
    'AOLTR' => .4,
    'Network Information API' => .3,
    'Battery Status API' => .2
);

// Get RNA Lables
$labels = array();
$n = 1;
foreach ($features as $k => $v) {
    $labels[$k] = "x" . $n;
    $n ++;
}

// Create Users
$users = array();
for($i = 0, $name = "A"; $i < 5; $i ++, $name ++) {
    $users[] = new Profile($name, $features);
}

// Generate Unknown User
$unknown = new Profile("Unknown", $features);

// Generate Unknown RNA
$unknownRNA = array(
    0 => array("o" => 1),
    1 => array("o" => - 1)
);

// Create RNA Values
foreach ($unknown->data as $item => $point) {
    $unknownRNA[0][$labels[$item]] = $point;
    $unknownRNA[1][$labels[$item]] = (- 1 * $point);
}

// Start Perception Class
$perceptron = new Perceptron();

// Train Results
$trainResult = $perceptron->train($unknownRNA, 1, 1);

// Find matches
foreach ($users as $name => &$profile) {
    // Use shorter labels
    $data = array_combine($labels, $profile->data);
    if ($perceptron->testCase($data, $trainResult) == true) {
        $score = $diff = 0;

        // Determing the score and diffrennce
        foreach ($unknown->data as $item => $found) {
            if ($unknown->data[$item] === $profile->data[$item]) {
                if ($profile->data[$item] > 0) {
                    $score += $features[$item];
                } else {
                    $diff += $features[$item];
                }
            }
        }
        // Ser score and diff
        $profile->setScore($score, $diff);
        $matchs[] = $profile;
    }
}

// Sort bases on score and Output
if (count($matchs) > 1) {
    usort($matchs, function ($a, $b) {
        // If score is the same use diffrence
        if ($a->score == $b->score) {
            // Lower the diffrence the better
            return $a->diff == $b->diff ? 0 : ($a->diff > $b->diff ? 1 : - 1);
        }
        // The higher the score the better
        return $a->score > $b->score ? - 1 : 1;
    });

    echo "<br />Possible Match ", implode(",", array_slice(array_map(function ($v) {
        return sprintf(" %s (%0.4f|%0.4f) ", $v->name, $v->score,$v->diff);
    }, $matchs), 0, 2));
} else {
    echo "<br />No match Found ";
}

เอาท์พุต:

Possible Match D (0.7416|0.16853),C (0.5393|0.2809)

Print_r จาก "D":

echo "<pre>";
print_r($matchs[0]);


Profile Object(
    [name] => D
    [data] => Array (
        [Real IP address] => -1
        [Used proxy IP address] => -1
        [HTTP Cookies] => 1
        [Session Cookies] => 1
        [3rd Party Cookies] => 1
        [Flash Cookies] => 1
        [PDF Bug] => 1
        [Flash Bug] => 1
        [Java Bug] => -1
        [Frequent Pages] => 1
        [Browsers Finger Print] => -1
        [Installed Plugins] => 1
        [URL] => -1
        [Cached PNG] => 1
        [System Fonts Detection] => 1
        [Localstorage] => -1
        [Geolocation] => -1
        [AOLTR] => 1
        [Network Information API] => -1
        [Battery Status API] => -1
    )
    [score] => 0.74157303370787
    [diff] => 0.1685393258427
    [base] => 8.9
)

หากตรวจแก้จุดบกพร่อง = true คุณจะสามารถที่จะเห็นการป้อนข้อมูล (เซนเซอร์และต้องการอยากมี) น้ำหนักเริ่มต้นขับ (เซนเซอร์, Sum, เครือข่าย), ข้อผิดพลาด, การแก้ไขและน้ำหนักชิงชนะเลิศ

+----+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+----+---------+---------+---------+---------+---------+---------+---------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------+
| o  | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | x10 | x11 | x12 | x13 | x14 | x15 | x16 | x17 | x18 | x19 | x20 | Bias | Yin | Y  | deltaW1 | deltaW2 | deltaW3 | deltaW4 | deltaW5 | deltaW6 | deltaW7 | deltaW8 | deltaW9 | deltaW10 | deltaW11 | deltaW12 | deltaW13 | deltaW14 | deltaW15 | deltaW16 | deltaW17 | deltaW18 | deltaW19 | deltaW20 | W1 | W2 | W3 | W4 | W5 | W6 | W7 | W8 | W9 | W10 | W11 | W12 | W13 | W14 | W15 | W16 | W17 | W18 | W19 | W20 | deltaBias |
+----+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+----+---------+---------+---------+---------+---------+---------+---------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------+
| 1  | 1  | -1 | -1 | -1 | -1 | -1 | -1 | 1  | 1  | 1   | 1   | 1   | 1   | 1   | -1  | -1  | -1  | -1  | 1   | 1   | 1    | 0   | -1 | 0       | -1      | -1      | -1      | -1      | -1      | -1      | 1       | 1       | 1        | 1        | 1        | 1        | 1        | -1       | -1       | -1       | -1       | 1        | 1        | 0  | -1 | -1 | -1 | -1 | -1 | -1 | 1  | 1  | 1   | 1   | 1   | 1   | 1   | -1  | -1  | -1  | -1  | 1   | 1   | 1         |
| -1 | -1 | 1  | 1  | 1  | 1  | 1  | 1  | -1 | -1 | -1  | -1  | -1  | -1  | -1  | 1   | 1   | 1   | 1   | -1  | -1  | 1    | -19 | -1 | 0       | 0       | 0       | 0       | 0       | 0       | 0       | 0       | 0       | 0        | 0        | 0        | 0        | 0        | 0        | 0        | 0        | 0        | 0        | 0        | 0  | -1 | -1 | -1 | -1 | -1 | -1 | 1  | 1  | 1   | 1   | 1   | 1   | 1   | -1  | -1  | -1  | -1  | 1   | 1   | 1         |
| -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | --  | --  | --  | --  | --  | --  | --  | --  | --  | --  | --  | --   | --  | -- | --      | --      | --      | --      | --      | --      | --      | --      | --      | --       | --       | --       | --       | --       | --       | --       | --       | --       | --       | --       | -- | -- | -- | -- | -- | -- | -- | -- | -- | --  | --  | --  | --  | --  | --  | --  | --  | --  | --  | --  | --        |
| 1  | 1  | -1 | -1 | -1 | -1 | -1 | -1 | 1  | 1  | 1   | 1   | 1   | 1   | 1   | -1  | -1  | -1  | -1  | 1   | 1   | 1    | 19  | 1  | 0       | 0       | 0       | 0       | 0       | 0       | 0       | 0       | 0       | 0        | 0        | 0        | 0        | 0        | 0        | 0        | 0        | 0        | 0        | 0        | 0  | -1 | -1 | -1 | -1 | -1 | -1 | 1  | 1  | 1   | 1   | 1   | 1   | 1   | -1  | -1  | -1  | -1  | 1   | 1   | 1         |
| -1 | -1 | 1  | 1  | 1  | 1  | 1  | 1  | -1 | -1 | -1  | -1  | -1  | -1  | -1  | 1   | 1   | 1   | 1   | -1  | -1  | 1    | -19 | -1 | 0       | 0       | 0       | 0       | 0       | 0       | 0       | 0       | 0       | 0        | 0        | 0        | 0        | 0        | 0        | 0        | 0        | 0        | 0        | 0        | 0  | -1 | -1 | -1 | -1 | -1 | -1 | 1  | 1  | 1   | 1   | 1   | 1   | 1   | -1  | -1  | -1  | -1  | 1   | 1   | 1         |
| -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | --  | --  | --  | --  | --  | --  | --  | --  | --  | --  | --  | --   | --  | -- | --      | --      | --      | --      | --      | --      | --      | --      | --      | --       | --       | --       | --       | --       | --       | --       | --       | --       | --       | --       | -- | -- | -- | -- | -- | -- | -- | -- | -- | --  | --  | --  | --  | --  | --  | --  | --  | --  | --  | --  | --        |
+----+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+----+---------+---------+---------+---------+---------+---------+---------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------+

x1 ถึง x20 แสดงถึงคุณสมบัติที่แปลงโดยรหัส

// Get RNA Labels
$labels = array();
$n = 1;
foreach ( $features as $k => $v ) {
    $labels[$k] = "x" . $n;
    $n ++;
}

นี่คือการสาธิตออนไลน์

คลาสที่ใช้:

class Profile {
    public $name, $data = array(), $score, $diff, $base;

    function __construct($name, array $importance) {
        $values = array(-1, 1); // Perception values
        $this->name = $name;
        foreach ($importance as $item => $point) {
            // Generate Random true/false for real Items
            $this->data[$item] = $values[mt_rand(0, 1)];
        }
        $this->base = array_sum($importance);
    }

    public function setScore($score, $diff) {
        $this->score = $score / $this->base;
        $this->diff = $diff / $this->base;
    }
}

คลาส Perceptron ดัดแปลง

class Perceptron {
    private $w = array();
    private $dw = array();
    public $debug = false;

    private function initialize($colums) {
        // Initialize perceptron vars
        for($i = 1; $i <= $colums; $i ++) {
            // weighting vars
            $this->w[$i] = 0;
            $this->dw[$i] = 0;
        }
    }

    function train($input, $alpha, $teta) {
        $colums = count($input[0]) - 1;
        $weightCache = array_fill(1, $colums, 0);
        $checkpoints = array();
        $keepTrainning = true;

        // Initialize RNA vars
        $this->initialize(count($input[0]) - 1);
        $just_started = true;
        $totalRun = 0;
        $yin = 0;

        // Trains RNA until it gets stable
        while ($keepTrainning == true) {
            // Sweeps each row of the input subject
            foreach ($input as $row_counter => $row_data) {
                // Finds out the number of columns the input has
                $n_columns = count($row_data) - 1;

                // Calculates Yin
                $yin = 0;
                for($i = 1; $i <= $n_columns; $i ++) {
                    $yin += $row_data["x" . $i] * $weightCache[$i];
                }

                // Calculates Real Output
                $Y = ($yin <= 1) ? - 1 : 1;

                // Sweeps columns ...
                $checkpoints[$row_counter] = 0;
                for($i = 1; $i <= $n_columns; $i ++) {
                    /** DELTAS **/
                    // Is it the first row?
                    if ($just_started == true) {
                        $this->dw[$i] = $weightCache[$i];
                        $just_started = false;
                        // Found desired output?
                    } elseif ($Y == $row_data["o"]) {
                        $this->dw[$i] = 0;
                        // Calculates Delta Ws
                    } else {
                        $this->dw[$i] = $row_data["x" . $i] * $row_data["o"];
                    }

                    /** WEIGHTS **/
                    // Calculate Weights
                    $this->w[$i] = $this->dw[$i] + $weightCache[$i];
                    $weightCache[$i] = $this->w[$i];

                    /** CHECK-POINT **/
                    $checkpoints[$row_counter] += $this->w[$i];
                } // END - for

                foreach ($this->w as $index => $w_item) {
                    $debug_w["W" . $index] = $w_item;
                    $debug_dw["deltaW" . $index] = $this->dw[$index];
                }

                // Special for script debugging
                $debug_vars[] = array_merge($row_data, array(
                    "Bias" => 1,
                    "Yin" => $yin,
                    "Y" => $Y
                ), $debug_dw, $debug_w, array(
                    "deltaBias" => 1
                ));
            } // END - foreach

            // Special for script debugging
             $empty_data_row = array();
            for($i = 1; $i <= $n_columns; $i ++) {
                $empty_data_row["x" . $i] = "--";
                $empty_data_row["W" . $i] = "--";
                $empty_data_row["deltaW" . $i] = "--";
            }
            $debug_vars[] = array_merge($empty_data_row, array(
                "o" => "--",
                "Bias" => "--",
                "Yin" => "--",
                "Y" => "--",
                "deltaBias" => "--"
            ));

            // Counts training times
            $totalRun ++;

            // Now checks if the RNA is stable already
            $referer_value = end($checkpoints);
            // if all rows match the desired output ...
            $sum = array_sum($checkpoints);
            $n_rows = count($checkpoints);
            if ($totalRun > 1 && ($sum / $n_rows) == $referer_value) {
                $keepTrainning = false;
            }
        } // END - while

        // Prepares the final result
        $result = array();
        for($i = 1; $i <= $n_columns; $i ++) {
            $result["w" . $i] = $this->w[$i];
        }

        $this->debug($this->print_html_table($debug_vars));

        return $result;
    } // END - train
    function testCase($input, $results) {
        // Sweeps input columns
        $result = 0;
        $i = 1;
        foreach ($input as $column_value) {
            // Calculates teste Y
            $result += $results["w" . $i] * $column_value;
            $i ++;
        }
        // Checks in each class the test fits
        return ($result > 0) ? true : false;
    } // END - test_class

    // Returns the html code of a html table base on a hash array
    function print_html_table($array) {
        $html = "";
        $inner_html = "";
        $table_header_composed = false;
        $table_header = array();

        // Builds table contents
        foreach ($array as $array_item) {
            $inner_html .= "<tr>\n";
            foreach ( $array_item as $array_col_label => $array_col ) {
                $inner_html .= "<td>\n";
                $inner_html .= $array_col;
                $inner_html .= "</td>\n";

                if ($table_header_composed == false) {
                    $table_header[] = $array_col_label;
                }
            }
            $table_header_composed = true;
            $inner_html .= "</tr>\n";
        }

        // Builds full table
        $html = "<table border=1>\n";
        $html .= "<tr>\n";
        foreach ($table_header as $table_header_item) {
            $html .= "<td>\n";
            $html .= "<b>" . $table_header_item . "</b>";
            $html .= "</td>\n";
        }
        $html .= "</tr>\n";

        $html .= $inner_html . "</table>";

        return $html;
    } // END - print_html_table

    // Debug function
    function debug($message) {
        if ($this->debug == true) {
            echo "<b>DEBUG:</b> $message";
        }
    } // END - debug
} // END - class

สรุป

การระบุผู้ใช้โดยไม่มีตัวระบุเฉพาะไม่ใช่งานตรงไปตรงมาหรือเรียบง่าย ขึ้นอยู่กับการรวบรวมข้อมูลสุ่มในปริมาณที่เพียงพอซึ่งคุณสามารถรวบรวมจากผู้ใช้ด้วยวิธีการต่างๆ

แม้ว่าคุณจะเลือกที่จะไม่ใช้โครงข่ายประสาทเทียม แต่อย่างน้อยฉันขอแนะนำให้ใช้ Simple Probability Matrix ที่มีลำดับความสำคัญและความเป็นไปได้ - และฉันหวังว่าโค้ดและตัวอย่างที่ให้ไว้ข้างต้นจะเพียงพอสำหรับการดำเนินการต่อไป


@Baba "การใช้ Blobs" เพื่อใช้ลายนิ้วมือบนเบราว์เซอร์หมายความว่าอย่างไร
billmalarky


1
@ บาบาจะใช้ลายนิ้วมือบนเบราว์เซอร์ได้อย่างไร? เพียงตรวจสอบว่ามีอะไรอยู่ในปัจจุบันในช่วงเวลาใด?
billmalarky

@Baba ทำงานได้ดีมากฉันพยายามมีกลยุทธ์หลายระดับในการระบุผู้ใช้มาโดยตลอด แต่อย่างที่คุณบอกว่าสามารถล้างแคชได้ IPs เปลี่ยนไปผู้ใช้ที่อยู่เบื้องหลังพร็อกซีหรือ NAT โดยเฉพาะคนเหล่านั้นคุกกี้ถูกลบ ฯลฯ แต่ถึงแม้จะใช้ความพยายามมากเพียงใด แต่ก็เป็นเรื่องที่น่าจะเป็นไปได้เช่นกันหากผู้ใช้ที่ไม่ดีใช้เบราว์เซอร์Tor ส่วนใหญ่ถ้าไม่ใช่กลยุทธ์การตรวจจับทั้งหมดที่กล่าวถึงจะไม่ได้ผล ฉันชอบbrowserleaks.comแต่ด้วย Tor ทั้งหมดกลับมาไม่ได้กำหนดหรือไม่รู้จัก
Mi-Creativity

หมายเหตุมีไว้เพื่อ"กำจัดฝุ่น"ออกจากอัญมณีของสิ่งพิมพ์นี้เท่านั้น: รายการลิงก์ที่เสีย ณ วันที่ 07.09.17: - Implement Bayesian inference using PHPทั้ง 3 ส่วน - Frequency vs Probability - Joint Probability -Input (Sensor & Desired), Initial Weights, Output (Sensor, Sum, Network), Error, Correction and Final Weights
Ziezi

28

เทคนิคนี้ (ในการตรวจสอบผู้ใช้เดียวกันโดยไม่ต้องคุกกี้ - หรือแม้กระทั่งไม่มีที่อยู่ IP) ที่เรียกว่าเบราว์เซอร์พิมพ์ลายนิ้วมือ โดยทั่วไปคุณจะรวบรวมข้อมูลเป็นข้อมูลเกี่ยวกับเบราว์เซอร์เท่าที่จะทำได้ - ผลลัพธ์ที่ดีขึ้นสามารถทำได้ด้วย javascript, แฟลชหรือ java (นามสกุลที่ติดตั้ง f.ex. แบบอักษร ฯลฯ ) หลังจากนั้นคุณสามารถจัดเก็บผลลัพธ์ที่แฮชได้หากต้องการ

มันไม่ผิด แต่:

83.6% ของเบราว์เซอร์ที่เห็นมีลายนิ้วมือที่เป็นเอกลักษณ์ ในกลุ่มที่เปิดใช้งาน Flash หรือ Java 94.2% นี่ไม่รวมคุกกี้!

ข้อมูลเพิ่มเติม:


ฉันคิดว่ามันยังคงเป็นคำตอบ หากคุณต้องการระบุอุปกรณ์คุณจะต้องได้รับข้อมูลเหล่านั้นเท่านั้น - f.ex. ระบบปฏิบัติการส่วนขยายทั่วไป (และเวอร์ชัน ') แบบอักษรที่ติดตั้ง ฯลฯ ...
pozs

สิ่งนี้จะไม่ได้ผลดี ทุกเบราว์เซอร์รองรับเซสชันและคุกกี้ ใช้เครื่องมือที่เหมาะสมกับงาน
Man Vs Code

1
@ slash197 แล้ว file cache ล่ะ? ฉันหมายถึงการใช้สื่อแฟลชโปร่งใส 1px x 1px พร้อมกับไฟล์ xml ที่มี id ที่สร้างขึ้นเฉพาะภายใน (ควรสร้าง xml หนึ่งครั้งบนเซิร์ฟเวอร์ก่อนที่จะดาวน์โหลดไปยัง HD ในเครื่องของผู้ใช้) ด้วยวิธีนี้แม้ว่าผู้ใช้จะลบคุกกี้หรือออกจากระบบ คุณยังสามารถมีสะพานได้โดยใช้วิธีการ action script sendAndLoad
Mbarry

ขั้นต่ำของการเปลี่ยนแปลงจะส่งผลต่อผลลัพธ์ของแฮช ตัวอย่างเช่นเวอร์ชันของเครื่องเล่นคลื่นกระแทก วิธีแก้ปัญหาที่เป็นไปได้ด้วยไฟล์แคช xml ที่เก็บไว้ในเครื่องพร้อมด้วยคีย์เฉพาะที่สร้างขึ้น + สื่อแฟลช 1px x 1px ที่ซ่อนอยู่ (สคริปต์การดำเนินการ) บนเบราว์เซอร์ด้วยวิธีนี้คุณจะกำจัดคุกกี้ปัญหาการหมดอายุของเซสชันหากเป็นปัญหาหลัก คุณยังสามารถมีสะพานเชื่อมระหว่างฐานข้อมูล sql ของคุณและคีย์บนเครื่องโลคัลของผู้ใช้
Mbarry

@Mbarry ฉันไม่ได้เป็นแฟนแฟลชเท่าไหร่ แต่ถ้าในเบราว์เซอร์มีโปรแกรมเสริมการปิดกั้นแฟลชเช่นฉันมีสื่อแฟลช 1x1 พิกเซลจะถูกปิดใช้งานฉันจะเป็นอะไรไหม?
slash197

7

การพิมพ์ลายนิ้วมือดังกล่าวข้างต้นใช้งานได้ แต่ยังคงมีอาการจุกเสียด

วิธีหนึ่งคือการเพิ่ม UID ลงใน url ของการโต้ตอบกับผู้ใช้แต่ละครั้ง

http://someplace.com/12899823/user/profile

โดยทุกลิงก์ในไซต์จะถูกปรับด้วยตัวปรับแต่งนี้ คล้ายกับวิธีที่ ASP.Net ใช้ในการทำงานโดยใช้ข้อมูล FORM ระหว่างเพจ


ฉันคิดอย่างนั้น แต่นั่นเป็นวิธีที่ง่ายที่สุดสำหรับผู้ใช้ในการแก้ไข
slash197

1
ไม่ใช่ของ id คือแฮชอ้างอิงตัวเอง ทำให้มีความปลอดภัยในการเข้ารหัส
Justin Alexander

นอกจากนี้วิธีนี้ใช้ได้เมื่อมีคนเรียกดูไซต์ แต่คุณจะเสนอให้จัดการกรณีนี้ได้อย่างไรเมื่อผู้ใช้ที่กลับมากลับมาหลังจากผ่านไปหนึ่งสัปดาห์แล้วพิมพ์ที่อยู่เว็บไซต์โดยไม่มีรหัส?
slash197

1
@ slash197 ในกรณีนั้นทำไมคุณไม่บอกให้ผู้ใช้เข้าสู่ระบบว่าจะเกิดอะไรขึ้นแม้ว่าผู้ใช้จะลบคุกกี้
Akash Kava

6

คุณเคยดูEvercookieหรือยัง? อาจใช้งานได้ในเบราว์เซอร์หรือไม่ก็ได้ สารสกัดจากไซต์ของพวกเขา

"หากผู้ใช้ทำอาหารบนเบราว์เซอร์หนึ่งและเปลี่ยนไปใช้เบราว์เซอร์อื่นตราบใดที่พวกเขายังมีคุกกี้ Local Shared Object คุกกี้จะสร้างซ้ำในเบราว์เซอร์ทั้งสอง"


ฉันสงสัยว่ามันใช้ได้กับ JavaScript ที่ปิดใช้งานหรือไม่ คุณมีประสบการณ์หรือไม่?
Voitcus

เรียกว่า evercookie ด้วยเหตุผลใดก็ตามมันจะใช้งานได้ไม่ว่าจะเกิดอะไรขึ้น แทบจะเป็นไปไม่ได้เลยที่พวกเขาจะเอาคุกกี้ออก
Alexis Tyler

มันจะไม่ทำงานไม่ว่าจะเกิดอะไรขึ้น จากบรรทัดแรกของคำอธิบาย: 'evercookie is a javascript API ... ' จะไม่ทำงานหากปิดใช้งานจาวาสคริปต์
gdw2

ไม่จำเป็นต้องปิดใช้งาน js ด้วยซ้ำ Ghostery และ uBlock ปล่อย evercookie
opengrid

4

คุณสามารถทำได้ด้วย png ที่แคชไว้มันจะค่อนข้างไม่น่าเชื่อถือ (เบราว์เซอร์ที่แตกต่างกันทำงานแตกต่างกันและจะล้มเหลวหากผู้ใช้ล้างแคช) แต่เป็นตัวเลือก

1: ตั้งค่าฐานข้อมูลที่เก็บ ID ผู้ใช้เฉพาะเป็นสตริงฐานสิบหก

2: สร้างไฟล์ genUser.php (หรือภาษาอะไรก็ได้) ที่สร้าง ID ผู้ใช้เก็บไว้ในฐานข้อมูลจากนั้นสร้างสีจริง. png จากค่าของสตริงฐานสิบหกนั้น (แต่ละพิกเซลจะเป็น 4 ไบต์) และส่งคืน ไปยังเบราว์เซอร์ อย่าลืมตั้งค่าประเภทเนื้อหาและส่วนหัวแคช

3: ใน HTML หรือ JS สร้างภาพเช่น <img id='user_id' src='genUser.php' />

4: วาดภาพนั้นลงบนผืนผ้าใบ ctx.drawImage(document.getElementById('user_id'), 0, 0);

5: อ่านไบต์ของรูปภาพนั้นโดยใช้ctx.getImageDataและแปลงจำนวนเต็มเป็นสตริงฐานสิบหก

6: นั่นคือรหัสผู้ใช้เฉพาะของคุณซึ่งตอนนี้แคชไว้ในคอมพิวเตอร์ผู้ใช้ของคุณ


เขาต้องการบางสิ่งที่สามารถติดตามผู้ใช้ "ข้ามเบราว์เซอร์" ซึ่งใช้ไม่ได้ที่นี่ (เบราว์เซอร์แต่ละตัวมีฐานข้อมูลแคชของตัวเอง)
EricLaw

คุณเห็นอยู่ที่ไหนคำถามของเขาเพียงถามว่า "ลืมบอกไปว่าต้องใช้งานข้ามเบราว์เซอร์ได้" นั่นคือทำงานในเบราว์เซอร์ใดก็ได้
hobberwickey

คำถามของเขาเขียนไม่ดี I'm after device recognitionเป็นของแถมสำหรับสิ่งที่เขาต้องการและเขาอธิบายไว้ที่นี่: stackoverflow.com/questions/15966812/…
EricLaw

2

คุณสามารถทำได้ด้วย etags แม้ว่าฉันจะไม่แน่ใจว่ากฎหมายนี้ถูกฟ้องร้องหรือไม่

หากคุณเตือนผู้ใช้ของคุณอย่างถูกต้องหรือหากคุณมีบางอย่างเช่นเว็บไซต์อินทราเน็ตอาจใช้ได้


Etags ไม่สามารถใช้งานข้ามเบราว์เซอร์ได้
slash197

1
Etags เป็นส่วนหนึ่งของข้อมูลจำเพาะ HTTP / 1.1 เบราว์เซอร์ยอดนิยมทั้งหมดรองรับ etags คุณจะต้องเขียนเบราว์เซอร์ของคุณเองเพื่อไม่ให้รองรับส่วนหัว ETag / If-None-Match
Brian McGinity

ฉันไม่ได้บอกว่ามันไม่รองรับฉันบอกว่ามันเข้ากันไม่ได้กับเบราว์เซอร์ข้าม หากแท็กถูกบันทึกใน Firefox จะไม่มีใน Chrome ดังนั้นเนื้อหาจะถูกดาวน์โหลดอีกครั้งเนื่องจากไม่มีแคช
slash197

ตอนนี้ฉันเข้าใจสิ่งที่คุณพูด คุณถูก. เบราว์เซอร์แต่ละตัวมีที่เก็บแคชเป็นของตัวเองดังนั้น etags ที่แตกต่างกัน
Brian McGinity

1

คุณอาจสร้างหยดเพื่อจัดเก็บตัวระบุอุปกรณ์ ...

ข้อเสียคือผู้ใช้ต้องดาวน์โหลด Blob ( คุณสามารถบังคับให้ดาวน์โหลดได้ ) เนื่องจากเบราว์เซอร์ไม่สามารถเข้าถึงระบบไฟล์เพื่อบันทึกไฟล์ได้โดยตรง

เอกสารอ้างอิง:

https://www.inkling.com/read/javascript-definitive-guide-david-flanagan-6th/chapter-22/blobs


1

จากสิ่งที่คุณพูด:

โดยทั่วไปแล้วฉันหลังจากการจดจำอุปกรณ์ไม่ใช่ผู้ใช้จริงๆ

วิธีที่ดีที่สุดคือส่งที่อยู่ mac ซึ่งเป็นรหัส NIC

คุณสามารถดูที่โพสต์นี้: ฉันจะรับ MAC และที่อยู่ IP ของไคลเอนต์ที่เชื่อมต่อใน PHP ได้อย่างไร


ขออภัย NIC ID สามารถปลอมแปลงได้ง่าย ไม่ใช่วิธีที่ดีที่สุดแน่นอน
asgs

@asgs การพิมพ์ลายนิ้วมือของเบราว์เซอร์อาจจะดีกว่าหรืออะไรจะเป็นวิธีที่ดีที่สุดในความคิดของคุณ?
Mehdi Karamosly

ไม่มีวิธีที่ดีที่สุดนั่นคือส่วนที่น่าเศร้าเกี่ยวกับเรื่องนี้ อย่างไรก็ตามสิ่งนั้นและการพิมพ์ลายนิ้วมือของเบราว์เซอร์ร่วมกับการศึกษาความน่าจะเป็นที่ Baba ได้นำเสนอข้างต้นน่าจะดีที่สุดในความคิดของฉัน
asgs

0

ไม่มีประสิทธิภาพ แต่อาจให้ผลลัพธ์ที่ต้องการคือการสำรวจ API ในฝั่งของคุณ มีกระบวนการเบื้องหลังในฝั่งไคลเอ็นต์ซึ่งส่งข้อมูลผู้ใช้เป็นช่วง ๆ คุณจะต้องมีตัวระบุผู้ใช้เพื่อส่งไปยัง API ของคุณ เมื่อคุณมีแล้วคุณสามารถส่งข้อมูลใด ๆ ที่เกี่ยวข้องกับตัวระบุเฉพาะนั้นได้

สิ่งนี้ขจัดความจำเป็นในการใช้คุกกี้และการจัดเก็บในตัวเครื่อง


0

ไม่อยากจะเชื่อเลยhttp://browserspy.dkยังไม่ได้กล่าวถึงที่นี่! ไซต์นี้อธิบายคุณลักษณะต่างๆมากมาย (ในแง่ของการจดจำรูปแบบ) ซึ่งสามารถใช้เพื่อสร้างตัวจำแนก

และสาเหตุสำหรับการประเมินคุณสมบัติฉันขอแนะนำให้สนับสนุน Vector Machines และlibsvmโดยเฉพาะ


0

ติดตามพวกเขาในระหว่างเซสชันหรือข้ามเซสชัน?

หากไซต์ของคุณเป็น HTTPS ทุกที่คุณสามารถใช้ TLS Session ID เพื่อติดตามเซสชันของผู้ใช้ได้


1
คำถามนี่คืออย่างไร?
user455318

-2
  1. สร้างปลั๊กอินดัมมี่ (nsapi) ข้ามแพลตฟอร์มและสร้างชื่อเฉพาะสำหรับชื่อปลั๊กอินหรือเวอร์ชันเมื่อผู้ใช้ดาวน์โหลด (เช่นหลังจากล็อกอิน)
  2. จัดเตรียมโปรแกรมติดตั้งสำหรับปลั๊กอิน / ติดตั้งตามนโยบาย

ซึ่งจะต้องให้ผู้ใช้ติดตั้งตัวระบุด้วยความเต็มใจ

เมื่อติดตั้งปลั๊กอินแล้วลายนิ้วมือของเบราว์เซอร์ (ที่เปิดใช้งานปลั๊กอิน) จะมีปลั๊กอินเฉพาะนี้ ในการส่งคืนข้อมูลไปยังเซิร์ฟเวอร์จำเป็นต้องใช้อัลกอริทึมในการตรวจจับปลั๊กอินบนฝั่งไคลเอ็นต์อย่างมีประสิทธิภาพมิฉะนั้นผู้ใช้ IE และ Firefox> = 28 จะต้องมีตารางการระบุที่ถูกต้องที่เป็นไปได้

สิ่งนี้ต้องใช้การลงทุนค่อนข้างสูงในเทคโนโลยีที่ผู้จำหน่ายเบราว์เซอร์อาจจะปิดตัวลง เมื่อคุณสามารถโน้มน้าวให้ผู้ใช้ของคุณติดตั้งปลั๊กอินได้อาจมีตัวเลือกเช่นติดตั้งพร็อกซีภายในเครื่องใช้ VPN หรือแพตช์ไดรเวอร์เครือข่าย

ผู้ใช้ที่ไม่ต้องการระบุตัวตน (หรือเครื่องของพวกเขา) มักจะหาทางป้องกัน


สวัสดียินดีต้อนรับสู่ stack overflow โปรดทราบ; this will require the user to willingly install the identifier.อาจจะไม่ใช่ความหมายของโปสเตอร์ต้นฉบับ (OP)
Stefan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.