3rsh1
/single dog/college student/ctfer
3rsh1's Blog

urlsplit存在的编码问题——记一次buuctf题目

基础

从何说起呢。

编码问题

IDNA(Internationalizing Domain Names in Applications)应用程序国际化域名
IDNA是一种以标准方式处理ASCII以外字符的一种机制,它从unicode中提取字符,并允许非ASCII码字符以允许使用的ASCII字符表示。
国际化域名(IDN)最初是由马丁·杜斯特于1996年12月提出。1998年在新加坡国立大学教授陈定炜的指导下,Tan Juay Kwang和Leong Kok Yong将其付诸实施。经过许多讨论和对比各种提案后,应用程序国际化域名(IDNA)被采纳为正式标准,并被用在许多顶级域名中。在IDNA中,“国际化域名”特指可以成功将IDNA转化为十进位制ASCII的域名。
也就是说,这种编码方式是可以识别unicode字符的,但是对于ascii码不能识别的unicode字符,它允许将其转换成ascii来表示。
也就是这种机制,可能会引入非预期的字符。当然这种机制存在于你访问url时,会自动执行,帮你定位url。

而这种转化机制又是怎么样的呢?

如下引用一下https://python3-cookbook.readthedocs.io
关于unicode规范化有以下几种形式:

unicode的规范化格式有几种,每种的处理方式有些不一样。  
    NFC  
    Unicode 规范化格式 C。如果未指定 normalization-type,那么会执行 Unicode 规范化。  
    NFD  
    Unicode 规范化格式 D。  
    NFKC  
    Unicode 规范化格式 KC。  
    NFKD  
    Unicode 规范化格式 KD。

在Unicode中,某些字符能够用多个合法的编码表示。为了说明,考虑下面的这个例子:

>>> s1 = 'Spicy Jalape\u00f1o'
>>> s2 = 'Spicy Jalapen\u0303o'
>>> s1
'Spicy Jalapeño'
>>> s2
'Spicy Jalapeño'
>>> s1 == s2
False
>>> len(s1)
14
>>> len(s2)
15

我们可以看到:这里的文本”Spicy Jalapeño”使用了两种形式来表示。
第一种使用整体字符”ñ”(U+00F1),第二种使用拉丁字母”n”后面跟一个”~”的组合字符(U+0303)。

在需要比较字符串的程序中使用字符的多种表示会产生问题。
为了修正这个问题,你可以使用unicodedata模块先将文本标准化:

>>> import unicodedata
>>> t1 = unicodedata.normalize('NFC', s1)
>>> t2 = unicodedata.normalize('NFC', s2)
>>> t1 == t2
True
>>> print(ascii(t1))
'Spicy Jalape\xf1o'
>>> t3 = unicodedata.normalize('NFD', s1)
>>> t4 = unicodedata.normalize('NFD', s2)
>>> t3 == t4
True
>>> print(ascii(t3))
'Spicy Jalapen\u0303o'
>>>

normalize()第一个参数指定字符串标准化的方式。 NFC表示字符应该是整体组成(比如可能的话就使用单一编码),而NFD表示字符应该分解为多个组合字符表示。

Python同样支持扩展的标准化形式NFKC和NFKD,它们在处理某些字符的时候增加了额外的兼容特性。比如:

>>> s = '\ufb01' # A single character
>>> s
'fi'
>>> unicodedata.normalize('NFD', s)
'fi'
# Notice how the combined letters are broken apart here
>>> unicodedata.normalize('NFKD', s)
'fi'
>>> unicodedata.normalize('NFKC', s)
'fi'
>>>

以上是,编码处理的问题。用 Punycode/IDNA 编码的 URL 使用 NFKC 规范化来分解字符。可能导致某些字符将新的段引入 URL。例如,在直接比较中,\ uFF03不等于,而是统一化为#,这会更改 URL 的片段部分。类似地,\u2100 统一化为a/c,它引入了路径段。接下来我们再看一下python中部分函数的表现:

>>> from urllib.parse import urlsplit
>>> u = "https://example.com\uFF03@bing.com"
#不处理的结果
>>> SplitResult(scheme='https', netloc='example.com#@bing.com', path='', query='', fragment='')
#规范化处理的结果 
>>>import unicodedata
>>>u2 = unicodedata.normalize('NFKC', u)
>>> urlsplit(u2)
SplitResult(scheme='https', netloc='example.com', path='', query='', fragment='@bing.com')
#特殊编码处理的结果
>>>u3 = u.encode("idna").decode("ascii")
>>> urlsplit(u3)
SplitResult(scheme='https', netloc='example.com', path='', query='', fragment='@bing.com')

函数urlsplit是不能很好的分别其中的unicode编码的#符。但是编码之后,#就被引入到了url内。urlparse也是如此。

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

题目

suuctf内复现的[SUCTF 2019]Pythonginx。
直接贴上源码:

@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
    url = request.args.get("url")
    host = parse.urlparse(url).hostname #www.3rsh1.com
    if host == 'suctf.cc':
        return "我扌 your problem? 111"
    parts = list(urlsplit(url))
    host = parts[1]             #www.3rsh1.com
    if host == 'suctf.cc':
        return "我扌 your problem? 222 " + host
    newhost = []
    for h in host.split('.'): 
        newhost.append(h.encode('idna').decode('utf-8'))    #对每个部分进行编码
    parts[1] = '.'.join(newhost)    #又join回来了 www.3rsh1.com
    #去掉 url 中的空格
    finalUrl = urlunsplit(parts).split(' ')[0] #又给组合回来了
    host = parse.urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return urllib.request.urlopen(finalUrl).read()#读取文件
    else:
        return "我扌 your problem? 333"

其中部分函数:

>>> from urllib import parse
>>> url = 'http://www.example.com/a?b&c=1#2'
>>> host = parse.urlparse(url).hostname #urlparse对url进行分割,host等于其中的hostname
>>> parse.urlparse(url)                 #查看一下效果
ParseResult(scheme='http', netloc='www.example.com', path='/a', params='', query='b&c=1', fragment='2')
>>> host                                #查看host的内容
'www.example.com'
>>> parts = list(parse.urlsplit(url))   #同样的,urlsplit也是分割url,并保存为列表
>>> parts                               #查看一下效果
['http', 'www.example.com', '/a', 'b&c=1', '2']
>>> host = parts[1]                     #相当于也是取其中的hostname
>>> host
'www.example.com'
>>> finalUrl = parse.urlunsplit(parts).split(' ')[0]    #urlunsplit拼接为url
>>> finalUrl                                            #查看一下效果
'http://www.example.com/a?b&c=1#2'
>>>

这里稍作补充:

from urllib.parse import urlparse
from urllib import request,parse
url='http://www.cwi.nl:80/%7Eguido/Python.html'
o = urlparse(url)
print(o)#ParseResult(scheme='http', netloc='www.cwi.nl:80', path='/%7Eguido/Python.html', params='', query='', fragment='')
print(o.hostname)#www.cwi.nl
print(parse.urlsplit(url))#SplitResult(scheme='http', netloc='www.cwi.nl:80', path='/%7Eguido/Python.html', query='', fragment='')

这里我们需要做的是,利用其中的urlopen函数来读取本地文件,那么我们需要做的就是让他前两个判断语句不成功,但是最后一个判断语句成功。
前两个判断语句和第三个判断语句中隔着一个规范化函数。前面介绍过其部分原理了,就是以idna形式即默认的nfc规范化,再编码成ascii码。
若是接受到的url值中存在特殊的unicode值,则会在经过编码后引入一些字符。特殊的ascii编码字符可以在这个网站上查找:
这里的nginx的配置文件的路径是在:/usr/local/nginx/conf/nginx.conf
访问之后构造payload:

file://suctf.c℆sr%2ffffffflag

访问即可得flag。
参考链接:

https://zhuanlan.zhihu.com/p/104885386?utm_source=wechat_session
https://blog.csdn.net/hiahiachang/article/details/105443688
https://xz.aliyun.com/t/6070
https://xz.aliyun.com/t/6135#toc-1
没有标签
首页      web安全      urlsplit存在的编码问题——记一次buuctf题目

发表评论

textsms
account_circle
email

3rsh1's Blog

urlsplit存在的编码问题——记一次buuctf题目
基础 从何说起呢。 编码问题 IDNA(Internationalizing Domain Names in Applications)应用程序国际化域名 IDNA是一种以标准方式处理ASCII以外字符的一种机制,它从unicode中提取字符…
扫描二维码继续阅读
2020-04-28