ล่ารายชื่อด้วย Google Forms
2020.09.23 · 1-min read · Tags: python, news, tutorial

เร็ว ๆ นี้หลายคนอาจจะเห็นข่าวเกี่ยวกับกลุ่มคนต่าง ๆ มาเชิญชวนให้ประชาชน "ลงชื่อ" เพื่อสนับสนุนหรือคัดค้านอะไรต่าง ๆ พอสมควร ซึ่ง platform ที่กลุ่มต่าง ๆ เลือกใช้ก็จะแตกต่างกันออกไป ขึ้นกับวัตถุประสงค์ของการลงชื่อนั้น ๆ

ถ้าหวังจะให้มีผลทางกฎหมาย ก็อาจจะต้องทำอะไรที่เป็นทางการนิดนึง เช่น ขอสำเนาบัตรประชาชนเพื่อยืนยันตัวตน มีการเซ็นชื่อในกระดาษจริง ๆ โดยอาจไปลงในสถานที่ที่กำหนดไว้ หรือส่งเอกสารทางไปรษณีย์

แม้ว่าระยะหลังเราอาจจะเห็นการลงชื่อออนไลน์มากขึ้น แต่ปัญหาสำคัญของการลงชื่อออนไลน์คือการยืนยันตัวตน เพราะต้องยอมรับว่าหลักฐานส่วนใหญ่ที่ใช้ในการลงชื่อออนไลน์ เช่น ที่อยู่ เบอร์โทรศัพท์ ฯลฯ​ นั้นพิสูจน์ได้ค่อนข้างยาก หรือถ้าพิสูจน์ได้ก็ต้องใช้กำลังคนค่อนข้างมาก แม้แต่เว็บลงชื่อออนไลน์ที่ได้รับความนิยม เช่น Change.org ก็ยังมีข่าวว่ามีการลงชื่อด้วยบัญชีปลอมอย่างต่อเนื่อง

Note

ประเทศไทยเราเองจริง ๆ แล้วก็มีพระราชบัญญัติว่าด้วยธุรกรรมทางอิเล็กทรอนิกส์ มาหลายฉบับด้วยกันแล้ว โดยใช้การยืนยันตัวตนด้วย biometrics ต่าง ๆ แต่ด้วยสาเหตุบางประการ การใช้งานก็ยังจำกัดอยู่ในการทำธุรกรรมกับธนาคารเท่านั้น เวลาประชาชนจะติดต่อภาคราชการหรือทำอะไรที่มีผลทางกฎหมาย ส่วนใหญ่ก็ยังต้องถ่ายสำเนาบัตรประชาชนอยู่ดี

การลงชื่อผ่าน Change.org แม้ว่าจะมีโอกาสเป็นบัญชีปลอมอยู่บ้าง แต่ผู้ที่ลงชื่อก็ต้องมี email เป็นของตัวเอง และทาง Change.org ก็มีระบบในการตรวจสอบบัญชีปลอมอยู่

ที่น่าประหลาดใจกว่าคือมีกลุ่มคนเลือกใช้ Google Forms ในการรวบรวมรายชื่อแบบไม่ต้อง sign in ด้วย ซ้ำร้ายพอมีคนไปลงชื่อมั่ว ก็บอกว่าอีกฝั่งหนึ่งใช้ "เทคโนโลยีขั้นสูง" โจมตี ทั้ง ๆ ที่จริง ๆ การเปิดแบบฟอร์มแบบไม่ require ให้ sign in นี่ก็ควรคำนึงถึงความเสี่ยงตรงนี้ตั้งแต่แรกแล้ว

บทความนี้จะแสดงให้ดูว่าการ "ปั่น" Google Forms ที่ไม่ require ให้คน sign in นั้น ง่ายซักแค่ไหน

Note

Google Forms มีตัวเลือกให้คนส่งแบบฟอร์มได้เพียงครั้งเดียว ซึ่งถ้าเลือกตัวเลือกนี้ Google ก็จะขอให้คนที่จะลงชื่อต้อง sign in ก่อน

สร้างแบบฟอร์มใหม่

ก่อนอื่นเรามาสร้างแบบฟอร์มใหม่กันก่อน โดยเข้าไปที่ Google Forms แล้วสร้างฟอร์มใหม่ได้เลย โดยอาจจะลองใส่ประเภทคำตอบหลาย ๆ แบบดูด้วย

ผมได้สร้างแบบฟอร์มไว้แล้วหน้าตาประมาณนี้ ซัึ่งมีด้วยกันทั้งสิ้น 6 คำถามด้วยกัน

มาแกะแบบฟอร์มดูกัน

ถ้าเราเปิด Developer Tools ขึ้นมา (สำหรับ Chrome กด ⌥⌘I บน Mac หรือ F12 บน Windows) ไปที่แทบ Elements แล้ว search หา "entry." (มี . ด้วยนะ) จะเห็นว่ามีทั้งหมด 6 อัน เท่ากับจำนวนคำถามเลย

entry.

ที่เจ๋งไปกว่านั้นคือ ถ้าเราลองพิมพ์อะไรไปในช่อง ก็จะเห็น attribute value เพิ่มขึ้นมาแบบ real time เลย ทั้งหมดนี้จะเป็นพื้นฐานของ object ที่เราจะส่งไปนั่นเอง

value

ส่งฟอร์มกันเลย

แบบเบสิก

วิธีส่งฟอร์มแบบง่ายที่สุด ก็ตามด้านล่างนี่เลย เรียก POST request ไป พร้อมกับ data ที่เป็น dict ตามแต่ข้อมูลที่เราอยากกรอกลงไป

1import requests
2
3url = 'https://docs.google.com/forms/d/e/1FAIpQLSckt2dk8DO9GmB66CkL7SdTV-oKCYAJEHRiJICcd1RgrsvKMw/formResponse'
4
5data = {
6 "entry.1176583672": "ไพธอน ลองฟอร์ม",
7 "entry.1219355088": "กศน.",
8 "entry.405752763": "สีน้ำเงิน",
9 "entry.824621759": "หญิง",
10 "entry.2022509876": ["ไจแอนท์", "โนบิตะ"],
11 "entry.724288185": "5"
12}
13
14r = requests.post(url, data=data)

สังเกตว่า url ที่ใช้ จะเป็น url ของแบบฟอร์มของเรา แต่เปลี่ยนด้านหลัง จากเดิมเป็น /viewform เป็น /formResponse แทน อีกอย่างคือ คำถามบางรูปแบบที่ให้เลือกได้หลายอย่าง เช่น checkbox ถ้าคำตอบจะมีหลายอย่างด้วย ก็จะต้องเป็น list

ลอง run เปิดดูผล ก็จะเห็นว่าได้ response ของเรามาแล้ว

value

วนลูป

แน่นอนว่าเราไม่ได้ต้องการส่งแค่ฟอร์มเดียว (ไม่งั้นจะมานั่งเขียน script ทำไม) การส่งหลาย ๆ อัน ก็แค่ใส่ requests.post เข้าไปในลูป

1import requests
2
3url = 'https://docs.google.com/forms/d/e/1FAIpQLSckt2dk8DO9GmB66CkL7SdTV-oKCYAJEHRiJICcd1RgrsvKMw/formResponse'
4
5data = {
6 "entry.1176583672": "ไพธอน ลองฟอร์ม",
7 "entry.1219355088": "กศน.",
8 "entry.405752763": "สีน้ำเงิน",
9 "entry.824621759": "หญิง",
10 "entry.2022509876": ["ไจแอนท์", "โนบิตะ"],
11 "entry.724288185": "5"
12}
13
14for i in range(0, 11):
15 r = requests.post(url, data=data)

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

วิธีแก้ก็คือการ random ข้อมูลที่เรากรอกเข้าไป โดยอาจเริ่มจาก list ของค่าที่เป็นไปได้ แล้วก็ย้าย loop ไปครอบทั้งหมดนั้น

1import requests
2import random
3
4namelist = ['สมชาย', 'สมหญิง', 'สมควร']
5lastnamelist = ['รักดี', 'ทวีศิลป์']
6schoollist = ['โรงเรียน ก.', 'โรงเรียน ข.', 'โรงเรียน ค.']
7genderlist = ['ชาย', 'หญิง']
8
9url = 'https://docs.google.com/forms/d/e/1FAIpQLSckt2dk8DO9GmB66CkL7SdTV-oKCYAJEHRiJICcd1RgrsvKMw/formResponse'
10
11for i in range(0, 11):
12 data = {
13 "entry.1176583672": random.choice(namelist) + ' ' + random.choice(lastnamelist),
14 "entry.1219355088": random.choice(schoollist),
15 "entry.405752763": "สีน้ำเงิน",
16 "entry.824621759": random.choice(genderlist),
17 "entry.2022509876": ["ไจแอนท์", "โนบิตะ"],
18 "entry.724288185": "5"
19 }
20
21 r = requests.post(url, data=data)

อันนี้เป็นตัวอย่างเร็ว ๆ เฉย ๆ เวลาทำจริง ๆ เราอาจจะไปดึงรายชื่อมาจากเว็บตั้งชื่อต่าง ๆ หรือชื่อโรงเรียนจากรายชื่อโรงเรียนต่าง ๆ

อีกเรื่อง คือการที่ IP ของเราอาจจะถูกแบน แม้ว่าในตอนนี้ Google จะไม่ได้เก็บ IP ของคนตอบแบบฟอร์มไว้ แต่การส่ง request ไปที่ Google รัว ๆ เป็นร้อยอันต่อนาที ก็อาจจะทำให้โดนแบนได้เหมือนกัน

วิธีแก้ แบบง่าย ๆ ก็คือการตั้ง sleep ไว้ จะได้ไม่ส่งรัวมาก ถ้าจะ advance ขึ้นมาหน่อย ก็คือการใช้ proxy ในการส่ง request โดย Python มี library ที่ให้ proxy ค่อนข้่างเยอะ เช่น fake-proxy เป็นต้น

ถ้าเราอยากทำให้ไฮโซขึ้นอีก ก็อาจจะทำหลาย ๆ thread แต่ละ thread ก็มี proxy ของตัวเอง ยิงไปเรื่อย ๆ (อาจจะหลัง sleep) จนกว่าจะใช้ไม่ได้ ก็ค่อยใช้ fake-proxy ไปหา proxy ใหม่มา

เรื่อง proxy นี่ขอพูดถึงประมาณนี้พอละกันนะครับ

บทสรุป

อันนี้เป็นโครงคร่าว ๆ ไม่ได้ตั้งใจทำมาก เพราะไม่ได้คิดว่าเป็นสิ่งที่ควรทำเพื่อไปแกล้งใคร แค่จะชี้ให้เห็นถึงความเสี่ยงที่อาจเกิดขึ้นเวลาใช้ Google Forms และความง่ายในการสแปม โดยเฉพาะถ้าเอามาล่ารายชื่อ ก็อาจถูกโจมตีด้วยวิธีพวกนี้ได้โดยง่าย และความถูกต้อง น่าเชื่อถือ ของจำนวนคนที่มาลงรายชื่อก็อาจจะถูกตั้งคำถามได้เช่นกัน

สุดท้ายนี้ ขอให้ทุกคน code ให้สนุกครับ ^^