PDO MySQL: ใช้ PDO :: ATTR_EMULATE_PREPARES หรือไม่?


117

นี่คือสิ่งที่ฉันได้อ่านเกี่ยวกับPDO::ATTR_EMULATE_PREPARES:

  1. PDO ของการเตรียมความพร้อมการแข่งขันจะดีกว่าสำหรับผลการดำเนินงานตั้งแต่ MySQL พื้นเมืองเตรียมทะลุแคชแบบสอบถาม
  2. ของ MySQL พื้นเมืองเตรียมจะดีกว่าสำหรับการรักษาความปลอดภัย (ป้องกัน SQL Injection)
  3. ของ MySQL พื้นเมืองเตรียมจะดีกว่าสำหรับการรายงานข้อผิดพลาด

ฉันไม่รู้ว่าข้อความเหล่านี้เป็นจริงแค่ไหน ข้อกังวลที่สุดของฉันในการเลือกอินเทอร์เฟซ MySQL คือการป้องกัน SQL Injection ข้อกังวลประการที่สองคือประสิทธิภาพ

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

ฉันใช้MySQL 5.1.61และPHP 5.3.2

ฉันควรPDO::ATTR_EMULATE_PREPARESเปิดใช้งานหรือไม่ มีวิธีใดบ้างที่จะมีทั้งประสิทธิภาพของแคชแบบสอบถามและความปลอดภัยของคำสั่งที่เตรียมไว้


3
สุจริต? เพียงแค่ใช้ MySQLi ต่อไป หากใช้งานได้แล้วโดยใช้คำสั่งที่เตรียมไว้ภายใต้นั้น PDO ก็เป็นชั้นนามธรรมที่ไม่มีจุดหมาย แก้ไข : PDO มีประโยชน์มากสำหรับแอปพลิเคชันกรีนฟิลด์ที่คุณไม่แน่ใจว่าฐานข้อมูลใดกำลังเข้าสู่ส่วนหลัง
jmkeyes

1
ขออภัยคำถามของฉันไม่ชัดเจนมาก่อน ฉันแก้ไขมันแล้ว แอปพลิเคชันไม่ได้ใช้คำสั่งที่เตรียมไว้ใน MySQLi ในขณะนี้ แค่ mysqli_run_query () จากสิ่งที่ฉันได้อ่านมา MySQLi ได้เตรียมคำสั่งไว้เช่นกันข้ามแคชแบบสอบถาม
Andrew Ensley

คำตอบ:


108

เพื่อตอบข้อกังวลของคุณ:

  1. MySQL> = 5.1.17 (หรือ> = 5.1.21 สำหรับPREPAREและEXECUTEงบ) สามารถใช้งบเตรียมในแคชแบบสอบถาม ดังนั้น MySQL + PHP เวอร์ชันของคุณจึงสามารถใช้คำสั่งที่เตรียมไว้กับแคชแบบสอบถาม อย่างไรก็ตามโปรดสังเกตข้อควรระวังสำหรับการแคชผลการค้นหาในเอกสาร MySQL มีแบบสอบถามหลายประเภทที่ไม่สามารถแคชได้หรือไม่มีประโยชน์แม้ว่าจะถูกแคชไว้ก็ตาม จากประสบการณ์ของฉันแคชการสืบค้นมักไม่ใช่ชัยชนะที่ยิ่งใหญ่มากนัก คิวรีและสคีมาจำเป็นต้องมีโครงสร้างพิเศษเพื่อใช้ประโยชน์สูงสุดจากแคช บ่อยครั้งที่การแคชระดับแอปพลิเคชันกลายเป็นสิ่งที่จำเป็นในระยะยาว

  2. การเตรียมแบบดั้งเดิมไม่ได้สร้างความแตกต่างใด ๆ สำหรับความปลอดภัย คำสั่งที่เตรียมหลอกจะยังคงหลบหนีค่าพารามิเตอร์การสืบค้นซึ่งจะทำในไลบรารี PDO ด้วยสตริงแทนบนเซิร์ฟเวอร์ MySQL โดยใช้โปรโตคอลไบนารี กล่าวอีกนัยหนึ่งรหัส PDO เดียวกันจะเสี่ยงต่อการโจมตีแบบแทรกซึม (หรือไม่เสี่ยง) เท่า ๆ กันโดยไม่คำนึงถึงEMULATE_PREPARESการตั้งค่าของคุณ ข้อแตกต่างเพียงอย่างเดียวคือการแทนที่พารามิเตอร์เกิดขึ้น - โดยEMULATE_PREPARESเกิดขึ้นในไลบรารี PDO โดยไม่EMULATE_PREPARESเกิดขึ้นบนเซิร์ฟเวอร์ MySQL

  3. โดยที่EMULATE_PREPARESคุณอาจไม่ได้รับข้อผิดพลาดทางไวยากรณ์ในเวลาเตรียมการแทนที่จะเป็นเวลาดำเนินการ กับEMULATE_PREPARESคุณจะได้รับข้อผิดพลาดทางไวยากรณ์ในเวลาการดำเนินการเพราะ PDO ไม่ได้มีแบบสอบถามให้กับ MySQL จนกว่าจะถึงเวลาการปฏิบัติ โปรดทราบว่าสิ่งนี้มีผลกับโค้ดที่คุณจะเขียน ! โดยเฉพาะอย่างยิ่งถ้าคุณใช้PDO::ERRMODE_EXCEPTION!

ข้อพิจารณาเพิ่มเติม:

  • มีค่าใช้จ่ายคงที่สำหรับ a prepare()(โดยใช้คำสั่งที่จัดเตรียมแบบเนทีฟ) ดังนั้นprepare();execute()คำสั่งที่มีการเตรียมแบบเนทีฟอาจช้ากว่าการออกแบบสอบถามข้อความธรรมดาโดยใช้คำสั่งจำลองที่เตรียมไว้เล็กน้อย ในระบบฐานข้อมูลจำนวนมากแผนการสืบค้นสำหรับ a prepare()จะถูกแคชไว้เช่นกันและอาจแชร์กับการเชื่อมต่อที่หลากหลาย แต่ฉันไม่คิดว่า MySQL จะทำเช่นนี้ ดังนั้นหากคุณไม่นำออบเจ็กต์คำสั่งที่เตรียมไว้มาใช้ซ้ำสำหรับการสืบค้นหลายครั้งการดำเนินการโดยรวมอาจช้า

ตามคำแนะนำสุดท้ายฉันคิดว่าด้วย MySQL + PHP เวอร์ชันเก่าคุณควรเลียนแบบข้อความที่เตรียมไว้ แต่ด้วยเวอร์ชันล่าสุดของคุณคุณควรปิดการจำลอง

หลังจากเขียนแอพสองสามตัวที่ใช้ PDO ฉันได้สร้างฟังก์ชันการเชื่อมต่อ PDO ซึ่งมีสิ่งที่ฉันคิดว่าเป็นการตั้งค่าที่ดีที่สุด คุณควรใช้สิ่งนี้หรือปรับแต่งการตั้งค่าที่คุณต้องการ:

/**
 * Return PDO handle for a MySQL connection using supplied settings
 *
 * Tries to do the right thing with different php and mysql versions.
 *
 * @param array $settings with keys: host, port, unix_socket, dbname, charset, user, pass. Some may be omitted or NULL.
 * @return PDO
 * @author Francis Avila
 */
function connect_PDO($settings)
{
    $emulate_prepares_below_version = '5.1.17';

    $dsndefaults = array_fill_keys(array('host', 'port', 'unix_socket', 'dbname', 'charset'), null);
    $dsnarr = array_intersect_key($settings, $dsndefaults);
    $dsnarr += $dsndefaults;

    // connection options I like
    $options = array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    );

    // connection charset handling for old php versions
    if ($dsnarr['charset'] and version_compare(PHP_VERSION, '5.3.6', '<')) {
        $options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES '.$dsnarr['charset'];
    }
    $dsnpairs = array();
    foreach ($dsnarr as $k => $v) {
        if ($v===null) continue;
        $dsnpairs[] = "{$k}={$v}";
    }

    $dsn = 'mysql:'.implode(';', $dsnpairs);
    $dbh = new PDO($dsn, $settings['user'], $settings['pass'], $options);

    // Set prepared statement emulation depending on server version
    $serverversion = $dbh->getAttribute(PDO::ATTR_SERVER_VERSION);
    $emulate_prepares = (version_compare($serverversion, $emulate_prepares_below_version, '<'));
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, $emulate_prepares);

    return $dbh;
}

26
Re # 2: แน่นอนว่าค่าที่ MySQL ได้รับเป็นพารามิเตอร์ (ไปยังคำสั่งที่เตรียมไว้ดั้งเดิม) ไม่ได้รับการแยกวิเคราะห์สำหรับ SQL เลย ? ดังนั้นความเสี่ยงของการฉีดจะต้องต่ำกว่าการใช้การจำลองการเตรียมของ PDO ซึ่งข้อบกพร่องใด ๆ ในการหลบหนี (เช่นปัญหาทางประวัติศาสตร์ที่mysql_real_escape_stringมีอักขระหลายไบต์) จะยังคงเปิดโอกาสให้มีการโจมตีด้วยการฉีด?
eggyal

2
@eggyal คุณกำลังตั้งสมมติฐานเกี่ยวกับวิธีการใช้งบที่เตรียมไว้ PDO อาจมีข้อบกพร่องในการจำลองการเตรียมการหลบหนี แต่ MySQL อาจมีข้อบกพร่องเช่นกัน AFAIK ไม่มีการค้นพบปัญหาเกี่ยวกับการเตรียมการจำลองซึ่งอาจทำให้ตัวอักษรพารามิเตอร์ผ่านไปโดยไม่ใช้ Escape
Francis Avila

2
คำตอบที่ยอดเยี่ยม แต่ฉันมีคำถาม: ถ้าคุณปิด EMULATION การดำเนินการจะไม่ช้าลงหรือ? PHP จะต้องส่งคำสั่งที่เตรียมไว้ไปยัง MySQL เพื่อตรวจสอบความถูกต้องจากนั้นจึงส่งพารามิเตอร์ ดังนั้นหากคุณใช้คำสั่งที่เตรียมไว้ 5 ครั้ง PHP จะคุยกับ MySQL 6 ครั้ง (แทนที่จะเป็น 5 ครั้ง) จะไม่ทำให้ช้าลงหรือ? นอกจากนี้ฉันคิดว่ามีโอกาสมากขึ้นที่ PDO อาจมีข้อบกพร่องในกระบวนการตรวจสอบความถูกต้องมากกว่า MySQL ...
Radu Murzea

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

6
+1 คำตอบที่ดี! แต่สำหรับเร็กคอร์ดหากคุณใช้การเตรียมแบบเนทีฟพารามิเตอร์จะไม่ถูกหลีกเลี่ยงหรือรวมเข้ากับคิวรี SQL แม้ในฝั่งเซิร์ฟเวอร์ MySQL เมื่อคุณดำเนินการและจัดหาพารามิเตอร์แบบสอบถามจะถูกแยกวิเคราะห์และเปลี่ยนเป็นโครงสร้างข้อมูลภายในใน MySQL อ่านบล็อกนี้โดยวิศวกรเพิ่มประสิทธิภาพ MySQL ที่อธิบายกระบวนการนี้: guilhembichot.blogspot.com/2014/05/…ฉันไม่ได้บอกว่านี่หมายความว่าการเตรียมแบบเนทีฟนั้นดีกว่าตราบใดที่เราเชื่อมั่นว่ารหัส PDO จะหลบหนีได้อย่างถูกต้อง (ซึ่งฉัน ทำ).
Bill Karwin

9

ระวังในการปิดการใช้งานPDO::ATTR_EMULATE_PREPARES(เปลี่ยนพื้นเมืองเตรียมความพร้อมใน) เมื่อคุณ PHP ไม่ได้รวบรวมกับpdo_mysqlmysqlnd

เนื่องจากlibmysqlฟังก์ชันเก่าไม่สามารถใช้งานร่วมกับฟังก์ชันบางอย่างได้อย่างสมบูรณ์จึงอาจทำให้เกิดข้อบกพร่องแปลก ๆ เช่น:

  1. การสูญเสียบิตที่สำคัญที่สุดสำหรับจำนวนเต็ม 64 บิตเมื่อรวมเป็นPDO::PARAM_INT(0x12345678AB จะถูกครอบตัดเป็น 0x345678AB บนเครื่อง 64 บิต)
  2. ไม่สามารถสร้างคำค้นหาง่ายๆเช่นLOCK TABLES(มีSQLSTATE[HY000]: General error: 2030 This command is not supported in the prepared statement protocol yetข้อยกเว้น)
  3. ต้องดึงข้อมูลแถวทั้งหมดจากผลลัพธ์หรือปิดเคอร์เซอร์ก่อนที่จะทำการค้นหาถัดไป (โดยมีmysqlndหรือจำลองการเตรียมการจะทำงานให้คุณโดยอัตโนมัติและไม่ซิงค์กับเซิร์ฟเวอร์ mysql)

จุดบกพร่องเหล่านี้ฉันพบในโครงการง่ายๆของฉันเมื่อย้ายไปยังเซิร์ฟเวอร์อื่นที่ใช้libmysqlสำหรับpdo_mysqlโมดูล อาจจะมีข้อบกพร่องอีกมากฉันไม่รู้ นอกจากนี้ผมทดสอบบน jessie 64bit เดเบียนสด, โรคจิตจดทะเบียนเกิดขึ้นเมื่อฉันและหายไปเมื่อฉันapt-get install php5-mysqlapt-get install php5-mysqlnd

เมื่อPDO::ATTR_EMULATE_PREPARESใดที่ถูกตั้งค่าเป็น true (เป็นค่าเริ่มต้น) - จุดบกพร่องเหล่านี้จะไม่เกิดขึ้นอยู่ดีเนื่องจาก PDO ไม่ได้ใช้คำสั่งที่เตรียมไว้เลยในโหมดนี้ ดังนั้นหากคุณใช้pdo_mysqlตามlibmysql(สตริงย่อย "mysqlnd" ไม่ปรากฏในฟิลด์ "เวอร์ชันไคลเอ็นต์ API" ของpdo_mysqlส่วนใน phpinfo) คุณไม่ควรPDO::ATTR_EMULATE_PREPARESปิด


3
ข้อกังวลนี้ยังใช้ได้ในปี 2562 หรือไม่!
oldboy

8

ฉันจะปิดการจำลองการเตรียมการเมื่อคุณใช้งาน 5.1 ซึ่งหมายความว่า PDO จะใช้ประโยชน์จากฟังก์ชันคำสั่งที่เตรียมไว้

PDO_MYSQL จะใช้ประโยชน์จากการสนับสนุนคำสั่งดั้งเดิมที่เตรียมไว้ใน MySQL 4.1 และสูงกว่า หากคุณกำลังใช้ไลบรารีไคลเอนต์ mysql เวอร์ชันเก่ากว่า PDO จะเลียนแบบให้คุณ

http://php.net/manual/en/ref.pdo-mysql.php

ฉันทิ้ง MySQLi สำหรับ PDO สำหรับคำสั่งที่มีชื่อที่เตรียมไว้และ API ที่ดีกว่า

อย่างไรก็ตามเพื่อความสมดุล PDO จะทำงานช้ากว่า MySQLi เล็กน้อย แต่ก็เป็นสิ่งที่ต้องจำไว้ ฉันรู้สิ่งนี้เมื่อตัดสินใจเลือกและตัดสินใจว่า API ที่ดีกว่าและการใช้มาตรฐานอุตสาหกรรมนั้นสำคัญกว่าการใช้ไลบรารีที่เร็วกว่าเล็กน้อยซึ่งเชื่อมโยงคุณกับเอ็นจิ้นเฉพาะ FWIW ฉันคิดว่าทีม PHP ก็มองไปในทางที่ดีที่ PDO มากกว่า MySQLi ในอนาคตเช่นกัน


ขอบคุณสำหรับข้อมูลนั้น การไม่สามารถใช้แคชการสืบค้นส่งผลกระทบต่อประสิทธิภาพของคุณอย่างไรหรือคุณเคยใช้มาก่อนหรือไม่
Andrew Ensley

ฉันไม่สามารถพูดได้ว่าเป็นกรอบงานที่ฉันใช้แคชในหลายระดับอยู่แล้ว คุณสามารถใช้ SELECT SQL_CACHE <rest of statement> อย่างชัดเจนได้ตลอดเวลา
Will Morgan

ไม่รู้ด้วยซ้ำว่ามีตัวเลือก SELECT SQL_CACHE อย่างไรก็ตามดูเหมือนว่ายังใช้ไม่ได้ จากเอกสาร: "ผลการสืบค้นจะถูกแคชหากแคชได้ ... " dev.mysql.com/doc/refman/5.1/en/query-cache-in-select.html
Andrew Ensley

ใช่. ขึ้นอยู่กับลักษณะของข้อความค้นหาแทนที่จะเป็นข้อมูลเฉพาะของแพลตฟอร์ม
Will Morgan

ฉันอ่านว่าหมายความว่า "ผลการสืบค้นจะถูกแคชเว้นแต่จะมีสิ่งอื่นป้องกันไม่ให้แคชได้" ซึ่ง - จากสิ่งที่ฉันอ่านจนถึงตอนนั้น - รวมคำสั่งที่เตรียมไว้ อย่างไรก็ตามด้วยคำตอบของ Francis Avila ฉันรู้ว่านั่นไม่เป็นความจริงสำหรับ MySQL เวอร์ชันของฉันอีกต่อไป
Andrew Ensley

6

ฉันขอแนะนำให้เปิดใช้งานการPREPAREเรียกฐานข้อมูลจริงเนื่องจากการจำลองไม่สามารถจับทุกอย่างได้ .. เช่นจะเตรียมINSERT;!

var_dump($dbh->prepare('INSERT;'));
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
var_dump($dbh->prepare('INSERT;'));

ผลลัพธ์

object(PDOStatement)#2 (1) {
  ["queryString"]=>
  string(7) "INSERT;"
}
bool(false)

ฉันยินดีที่จะทำการตีประสิทธิภาพสำหรับโค้ดที่ใช้งานได้จริง

FWIW

เวอร์ชัน PHP: PHP 5.4.9-4ubuntu2.4 (cli)

MySQL เวอร์ชัน: 5.5.34-0ubuntu0.0


มันเป็นจุดที่น่าสนใจ ฉันเดาว่าการจำลองจะเลื่อนการแยกวิเคราะห์ฝั่งเซิร์ฟเวอร์ไปยังขั้นตอนการดำเนินการ แม้ว่าจะไม่ใช่เรื่องใหญ่ (ในที่สุด SQL ที่ไม่ถูกต้องก็จะล้มเหลว) แต่ก็ยังดีกว่าที่จะปล่อยให้prepareทำงานตามที่ควรจะเป็น (นอกจากนี้ฉันมักจะคิดว่าตัวแยกวิเคราะห์พารามิเตอร์ฝั่งไคลเอ็นต์จำเป็นต้องมีจุดบกพร่องของตัวเอง)
ÁlvaroGonzález

1
IDK หากคุณสนใจ แต่นี่เป็นบทความเล็ก ๆ น้อย ๆเกี่ยวกับพฤติกรรมปลอมอื่น ๆ ที่ฉันสังเกตเห็นด้วย PDO ที่นำฉันไปสู่โพรงกระต่ายนี้เพื่อเริ่มต้น ดูเหมือนว่าขาดการจัดการแบบสอบถามหลายรายการ
quickshiftin

ฉันเพิ่งดูไลบรารีการย้ายข้อมูลบางส่วนใน GitHub ... คุณรู้อะไรบ้างอันนี้ค่อนข้างเหมือนกับบล็อกโพสต์ของฉัน
quickshiftin

6

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

สำหรับข้อมูลเพิ่มเติมโปรดดูคำตอบที่ยอมรับสำหรับคำถามนี้: PHP + PDO + MySQL: ฉันจะส่งคืนคอลัมน์จำนวนเต็มและตัวเลขจาก MySQL เป็นจำนวนเต็มและตัวเลขใน PHP ได้อย่างไร .


5

เหตุใดจึงเปลี่ยนการจำลองเป็น 'เท็จ'

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

ข้อโต้แย้งหลักในการใช้เอ็นจิ้นฐานข้อมูลสำหรับการจัดเตรียม vs PDO คือการเดินทางสองครั้งไปยังเซิร์ฟเวอร์ - รายการหนึ่งสำหรับการเตรียมการและอีกครั้งสำหรับพารามิเตอร์ที่จะส่งผ่าน - แต่ฉันคิดว่าการรักษาความปลอดภัยที่เพิ่มเข้ามานั้นคุ้มค่า นอกจากนี้อย่างน้อยในกรณีของ MySQL การแคชแบบสอบถามไม่ได้เป็นปัญหาตั้งแต่เวอร์ชัน 5.1

https://tech.michaelseiler.net/2016/07/04/dont-emulate-prepared-statements-pdo-mysql/


1
การแคชแบบสอบถามจะหายไปแล้ว: แคชแบบสอบถามเลิกใช้แล้วเมื่อ MySQL 5.7.20 และถูกลบออกใน MySQL 8.0
ÁlvaroGonzález

0

สำหรับบันทึก

PDO :: ATTR_EMULATE_PREPARES = true

มันสามารถสร้างผลข้างเคียงที่น่ารังเกียจ มันสามารถคืนค่า int เป็นสตริง

PHP 7.4, pdo พร้อม mysqlnd

เรียกใช้แบบสอบถามด้วย PDO :: ATTR_EMULATE_PREPARES = true

คอลัมน์:
ประเภทid : จำนวนเต็ม
ค่า: 1

เรียกใช้แบบสอบถามด้วย PDO :: ATTR_EMULATE_PREPARES = false

คอลัมน์: id
Type: string
ค่า: "1"

ไม่ว่าในกรณีใดค่าทศนิยมจะส่งคืนสตริงเสมอโดยไม่คำนึงถึงการกำหนดค่า :-(


ค่าทศนิยมจะส่งกลับเสมอสตริงเป็นวิธีเดียวที่ถูกต้อง
สามัญสำนึกของคุณ

ใช่จากมุมมองของ MySQL แต่ผิดที่ฝั่ง PHP ทั้ง Java และ C # ถือว่าทศนิยมเป็นค่าตัวเลข
magallanes

ไม่มันไม่ใช่ มันถูกต้องสำหรับวิทยาการคอมพิวเตอร์ทั้งหมด ถ้าคุณคิดว่ามันผิดคุณก็ต้องมีความแม่นยำแบบอื่น
สามัญสำนึกของคุณ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.