[SUCTF 2019]Pythonginx
# 定义一个路由,处理/getUrl路径的GET和POST请求
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():# 从请求参数中获取名为"url"的参数值url = request.args.get("url")# 使用urlparse解析URL,获取主机名(hostname)host = parse.urlparse(url).hostname# 第一次检查:如果主机名是suctf.cc,返回特定字符串if host == 'suctf.cc':return "我扌 your problem? 111"# 使用urlsplit分割URL为多个部分,并转换为列表parts = list(urlsplit(url))# 从分割后的部分中获取主机部分host = parts[1]# 第二次检查:如果主机是suctf.cc,返回特定字符串和主机名if host == 'suctf.cc':return "我扌 your problem? 222 " + host# 初始化一个列表用于存储处理后的主机部分newhost = []# 遍历主机名按"."分割后的每个部分for h in host.split('.'):# 对每个部分进行IDNA编码后再解码为UTF-8newhost.append(h.encode('idna').decode('utf-8'))# 将处理后的主机部分重新用"."连接parts[1] = '.'.join(newhost)# 重新组合URL并去掉空格finalUrl = urlunsplit(parts).split(' ')[0]# 再次解析处理后的URL,获取主机名host = parse.urlparse(finalUrl).hostname# 第三次检查:如果主机名是suctf.cc,则读取该URL内容并返回if host == 'suctf.cc':return urllib.request.urlopen(finalUrl).read()# 否则返回特定字符串else:return "我扌 your problem? 333"</code><!-- Dont worry about the suctf.cc. Go on! --><!-- Do you know the nginx? -->
这个代码主要功能就是对url进行一个过滤,阻止访问suctf.cc。提示中有nginx,说明采用的是该web服务器。所以这道题的考点应该就是利用python中的相关url处理函数与nginx解析url的机制差异绕过过滤访问suctf.cc。由于对这两者都不太了解,直接看答案吧......
跟我想的不一样,这个python过滤代码本身就存在漏洞。
关键函数示例详解:
urlparse.urlparse()
- 示例:
urlparse("https://www.example.com:8080/path?name=test#top")
- 返回对象的主要属性:
scheme
→ "https"netloc
→ "www.example.com:8080"path
→ "/path"params
→ ""query
→ "name=test"fragment
→ "top"hostname
→ "www.example.com"(自动提取主机名,忽略端口)urlsplit()
- 与 urlparse 类似,但不解析 params 部分
- 示例:
urlsplit("http://user:pass@example.com/path;params?query#fragment")
- 返回:
SplitResult(scheme='http', netloc='user:pass@example.com', path='/path;params', query='query', fragment='fragment')
- 注意:params 部分被包含在 path 中,而不是单独作为一个部分
str.encode('idna')
- IDNA 编码用于处理国际化域名
- 示例 1:
" Müller ".encode('idna')
→ 输出b'xn--mller-kva'
(处理德语变音字符)- 示例 2:
"例子.中国".encode('idna')
→ 输出b'xn--fsq650b.xn--fiqs8s'
- 解码后:
b'xn--fsq650b'.decode('utf-8')
→ "xn--fsq650b"urlunsplit()
- 将分割后的 URL 部分重新组合
- 示例:
urlunsplit(('https', 'example.com', '/path', 'id=1', 'section'))
- 输出:
https://example.com/path?id=1#section
urllib.request.urlopen()
- 打开 URL 并获取内容
- 示例:
urllib.request.urlopen("https://example.com").read()
- 会返回该 URL 的 HTML 内容字节流
- 注意:这是一个阻塞操作,会等待服务器响应
我不清楚清楚第二次检查的必要性是什么。第一次检查是hostname的过滤,第二次检查是netloc的过滤。难道存在某种情况使得hostname不是suctf.cc然而netloc却是。算了不管了。
这道题的漏洞在于
for h in host.split('.'):
# 对每个部分进行IDNA编码后再解码为UTF-8
newhost.append(h.encode('idna').decode('utf-8'))
可能存在某个非ASCII码字符经过该种编码后成为一个ASCII码字符
chars = ['s', 'u', 'c', 't', 'f']
for c in chars:for i in range(0x7f, 0x10FFFF):try:char_i = chr(i).encode('idna').decode('utf-8')if char_i == c:print('ASCII: {} Unicode: {} Number: {}'.format(c, chr(i), i))except:pass
用ˢuct.cc绕过就好。
在 Python 的
urllib.request.urlopen()
函数中,如果要访问本地文件,需要传入带有file://
协议前缀的本地文件路径作为参数。具体格式如下:
- 对于绝对路径:
file:///绝对路径
(注意协议后有三个斜杠)- 对于相对路径:
file://相对路径
或file://./相对路径
nginx重要文件的位置:
配置文件存放目录:/etc/nginx主配置文件:/etc/nginx/conf/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx
配置文件目录为:/usr/local/nginx/conf/nginx.conf
总结一下:h.encode('idna').decode('utf-8')该种编码方式能将部分非ASCII字符转换成ASCII字符从而绕过一些过滤。
Web 题目中,flag 通常与网站配置、服务器文件系统或应用逻辑相关,常见位置包括:
-
网站根目录或子目录
- 如
flag.txt
flag.php
flag.html
直接存放在网站根目录(/var/www/html/
、/var/www/
等)或子目录(/static/
、/admin/
等)。 - 示例:
/var/www/html/flag.txt
、/var/www/secret/flag.php
。
- 如
-
服务器配置文件相关目录
- 如
/etc/apache2/
、/etc/nginx/
下的配置文件(可能包含 flag 注释或路径)。 - 示例:/etc/nginx/conf/nginx.conf。
- 如
-
用户目录
- 网站运行用户(如
www-data
、apache
)的家目录,如/home/www-data/flag.txt
、/root/flag.txt
(若权限泄露可访问)。
- 网站运行用户(如
-
临时文件目录
- Linux 临时目录
/tmp/
或/var/tmp/
,可能存在临时生成的含 flag 文件,如/tmp/flag_123.tmp
。
- Linux 临时目录
-
环境变量或配置文件
- 网站框架的配置文件(如
config.php
、settings.py
)中直接硬编码 flag,或引用环境变量(如FLAG=xxx
)。 - 示例:
/var/www/config.php
中$flag = "flag{xxx}"
。
- 网站框架的配置文件(如
-
数据库中
- 部分题目会将 flag 存放在数据库表中(如
flag
表、users
表的某个字段)。
- 部分题目会将 flag 存放在数据库表中(如