การตรวจสอบโฆษณา (Ad Verification) ใช้งานไม่ได้แล้ว ผลการตรวจสอบอุตสาหกรรมปี 2023 พบว่า 60-80 เปอร์เซ็นต์ของโฆษณาแบบโปรแกรมเมติก (programmatic ad impressions) ที่แสดงผ่านเอ็กซ์เชนจ์หลัก ๆ นั้นแสดงครีเอทีฟที่แตกต่างกันให้กับบอตตรวจสอบ (verification bots) และผู้ใช้จริง นี่คือการปิดบัง (cloaking) — และมันบ่อนทำลายรายงานความปลอดภัยของแบรนด์ทุกฉบับที่คุณเคยอ่าน วิธีแก้ไขคือต้องปฏิบัติต่อครอว์เลอร์ตรวจสอบเหมือนผู้โจมตี: ส่งผ่านพร็อกซี HTTP, ปรับเปลี่ยนลายนิ้วมือ (fingerprint) และเปรียบเทียบเนื้อหาที่แสดงผลกับค่าพื้นฐานที่ปลอดภัย
การปิดบังเลือกเป้าหมายอย่างไร
การปิดบังอาศัยสัญญาณสามอย่าง: User-Agent, X-Forwarded-For (หรือ IP โดยตรง) และ Referer เซิร์ฟเวอร์โฆษณาที่เป็นอันตรายจะตรวจสอบคำขอที่เข้ามาและตัดสินว่าผู้เยี่ยมชมเป็นบอตตรวจสอบหรือมนุษย์ บอต — เช่นจาก Moat, Integral Ad Science หรือ DoubleVerify — ส่งส่วนหัว (headers) ที่คาดเดาได้ จากนั้นเซิร์ฟเวอร์จะแสดงครีเอทีฟที่สะอาดและปลอดภัยต่อแบรนด์ให้กับบอต และแสดงครีเอทีฟที่เป็นอันตรายหรือไม่เหมาะสมให้กับคนอื่น ความแตกต่างนี้จะมองไม่เห็นในแดชบอร์ดของผู้ตรวจสอบ
ตัวอย่างในโลกจริง ได้แก่ เนื้อหาสำหรับผู้ใหญ่ โฆษณาชวนเชื่อทางการเมือง หรือการเปลี่ยนเส้นทางไปยังมัลแวร์ที่แสดงเฉพาะผู้ใช้มือถือในพื้นที่ทางภูมิศาสตร์ที่เฉพาะเจาะจง ผู้โจมตีจะตรวจสอบ User-Agent สำหรับ “Mozilla/5.0 (Linux; Android …)” และ X-Forwarded-For สำหรับช่วง IP ที่เป็นของผู้ให้บริการตรวจสอบที่รู้จัก หาก IP ตรงกัน โฆษณาจะปลอดภัย หากไม่ตรง ผู้ใช้จะได้รับเพย์โหลด
การใช้ MITM Proxy เพื่อตรวจจับความแตกต่าง
วิธีการตรวจจับที่เชื่อถือได้มากที่สุดคือการรันครอว์เลอร์ตรวจสอบของคุณเองผ่านพร็อกซี HTTP แบบโปร่งใส — mitmproxy หรือ Burp Suite — และเปรียบเทียบการตอบสนองกับคำขอควบคุมที่ส่งโดยไม่มีพร็อกซี พร็อกซีช่วยให้คุณบันทึกเนื้อหาการตอบสนองดิบและปรับเปลี่ยนส่วนหัวแบบเรียลไทม์ คุณสามารถเล่นซ้ำคำขอเดียวกันด้วย User-Agent หรือ X-Forwarded-For ที่แตกต่างกัน และดูว่าเซิร์ฟเวอร์โฆษณาเปลี่ยนครีเอทีฟหรือไม่
นี่คือสคริปต์ mitmproxy ขนาดเล็กที่บันทึกความแตกต่างระหว่างสองคำขอไปยัง URL เดียวกันด้วย user agent ที่แตกต่างกัน:
# save as check_cloak.py
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
if "adserver.example.com" in flow.request.pretty_host:
ua = flow.request.headers.get("User-Agent", "")
if "Android" in ua:
flow.request.headers["X-Forwarded-For"] = "1.2.3.4" # bot IP
else:
flow.request.headers["X-Forwarded-For"] = "5.6.7.8" # user IP
รันด้วย mitmproxy -s check_cloak.py --listen-port 8080 จากนั้นชี้เบราว์เซอร์หรือ curl ไปที่พร็อกซี เปรียบเทียบเนื้อหาการตอบสนอง — หาก HTML, รูปภาพ หรือ JavaScript แตกต่างกัน แสดงว่าคุณมีครีเอทีฟที่ถูกปิดบัง
การครอวล์ตามพื้นที่ทางภูมิศาสตร์ด้วยการหมุนพร็อกซี
การปิดบังมักใช้ GeoIP เป็นตัวแยกแยะเพิ่มเติม โฆษณาอาจแสดงครีเอทีฟที่สะอาดให้กับคำขอที่มาจากสหรัฐอเมริกา แต่เปลี่ยนเป็นครีเอทีฟที่เป็นอันตรายสำหรับผู้ใช้ในเอเชียตะวันออกเฉียงใต้หรือยุโรปตะวันออก เพื่อตรวจจับสิ่งนี้ คุณต้องครอวล์ URL โฆษณาเดียวกันจากจุดสิ้นสุดทางภูมิศาสตร์หลายจุด การใช้พูลของพร็อกซีที่อยู่อาศัย (เช่น BrightData, Oxylabs) หรือเชนพร็อกซี SOCKS5 ช่วยให้คุณตั้งค่า X-Forwarded-For และ TCP source IP พร้อมกัน
ใช้ curl กับพร็อกซีและส่วนหัวที่กำหนดเองเพื่อจำลองผู้ใช้มือถือในภูมิภาคเป้าหมาย:
curl -x socks5://user:pass@proxy-us-east:1080 \
-H "User-Agent: Mozilla/5.0 (Linux; Android 13; Pixel 7)" \
-H "X-Forwarded-For: 203.0.113.50" \
-H "Referer: https://example.com/article" \
-o response_us.html \
https://adserver.example.com/ad
curl -x socks5://user:pass@proxy-vietnam:1080 \
-H "User-Agent: Mozilla/5.0 (Linux; Android 13; Pixel 7)" \
-H "X-Forwarded-For: 42.112.0.1" \
-H "Referer: https://example.com/article" \
-o response_vn.html \
https://adserver.example.com/ad
เปรียบเทียบสองไฟล์ ความแตกต่างใด ๆ ในแท็ก <script> หรือแอตทริบิวต์ src ของรูปภาพบ่งชี้ถึงการปิดบังตามพื้นที่ทางภูมิศาสตร์
ความแตกต่างของครีเอทีฟระหว่างมือถือและเดสก์ท็อป
การปิดบังมักกำหนดเป้าหมายไปที่ทราฟฟิกมือถือ เนื่องจากผู้ใช้มือถือมีแนวโน้มที่จะตรวจสอบคำขอเครือข่ายน้อยกว่า ครอว์เลอร์ตรวจสอบที่เลียนแบบเฉพาะเดสก์ท็อปเบราว์เซอร์จะพลาดสิ่งนี้โดยสิ้นเชิง คุณต้องส่งคำขอด้วยสตริง User-Agent ทั้งสองแบบและเปรียบเทียบการตอบสนอง รูปแบบที่พบบ่อย: การตอบสนองจากเดสก์ท็อปมีแบนเนอร์ 300x250 มาตรฐาน ในขณะที่การตอบสนองจากมือถือโหลดอินเตอร์สติเชียลแบบเต็มหน้าจอที่เปลี่ยนเส้นทางไปยังหน้า phishing
ใช้เครื่องมือเช่น diff หรือ jq เพื่อเปรียบเทียบการตอบสนอง JSON สำหรับ HTML ให้ใช้ htmlq หรือ pup เพื่อแยกองค์ประกอบเฉพาะ กุญแจสำคัญคือการทำให้การเปรียบเทียบเป็นอัตโนมัติในเมทริกซ์ของ user agent, IP geo และ referrer ระบบที่ใช้งานจริงระบบหนึ่งที่ผมสร้างขึ้นรัน 16 คำขอแบบขนานต่อหน่วยโฆษณา และแจ้งเตือนเมื่อมีความแปรผันเกินเกณฑ์ขนาดไบต์ 5%
ข้อแลกเปลี่ยนและข้อจำกัด
วิธีการนี้ไม่สมบูรณ์แบบ เซิร์ฟเวอร์โฆษณาสามารถตรวจจับช่วง IP ของพร็อกซีและแสดงครีเอทีฟที่สะอาดให้กับทางออกของพร็อกซีที่รู้จัก — เช่นเดียวกับที่ตรวจจับบอตตรวจสอบ การหมุนพร็อกซีที่อยู่อาศัยช่วยได้แต่เพิ่มเวลาแฝงและต้นทุน นอกจากนี้ การปิดบังบางอย่างขึ้นอยู่กับเวลา: ครีเอทีฟที่เป็นอันตรายจะปรากฏหลังจากดีเลย์หรือหลังจากเหตุการณ์ JavaScript ที่คำขอ curl ธรรมดาไม่สามารถทริกเกอร์ได้ ในกรณีเหล่านี้คุณต้องใช้ headless browser (Puppeteer, Playwright) หลังพร็อกซี ซึ่งเพิ่มความซับซ้อนและความสามารถในการระบุลายนิ้วมือ
อย่างไรก็ตาม หลักการสำคัญยังคงอยู่: หากคุณไม่สามารถสร้างการตอบสนองที่เหมือนกันทุกประการในชุดลายนิ้วมือของไคลเอนต์ที่หลากหลาย โฆษณานั้นก็ไม่น่าเชื่อถือ พร็อกซี HTTP ช่วยให้คุณควบคุมเพื่อสร้างลายนิ้วมือเหล่านั้นโดยทางโปรแกรม เริ่มต้นด้วยสคริปต์ mitmproxy ง่าย ๆ และจุดสิ้นสุดพร็อกซีสองสามจุด แค่นี้ก็จะตรวจจับแคมเปญปิดบังแบบใช้ความพยายามต่ำส่วนใหญ่ได้ — และมันไม่เสียค่าใช้จ่ายใด ๆ นอกจากโค้ด Python สองสามบรรทัด