py4h4sher
打开就一个 OK
用御剑扫了一波,什么都没有
fuzz了一圈,就什么都没有
回过头来看了下题,hitcon,那没事了,就当作学习新 姿势 知识了
看了原题,要访问/cgi-bin/py4h4sher
才能到题目入口
点击Get Source获得源码:
#!/usr/bin/python
# coding: utf-8
import os
import re
import sys
import cgi
import hashlib
from urllib import unquote
from passlib.utils.pbkdf2 import pbkdf2
sys.path.append('/home/www-data/secret_file')
from secret_file import SECRET # 160 bytes secret
from secret_file import FLAG
print('Content-Type: text/html
')
def _pbkdf2(text):
return pbkdf2(text, 'noggnogg', 1337).encode('hex').lower()
def _md5(text):
return hashlib.md5( text ).hexdigest().lower()
def getenv(name):
return unquote( os.environ.get(name) ) or ''
def gotoFail():
print('goto fail')
print
exit()
def m_hash(password):
nr = int( 'P0W5'.encode('hex'), 16 )
add = 7
nr2 = 305419889
for c in (ord(x) for x in password if x not in (' ', ' ')):
nr^= (((nr & 63)+add)*c)+ (nr << 8) & 0xFFFFFFFF
nr2= (nr2 + ((nr2 << 8) ^ nr)) & 0xFFFFFFFF
add= (add + c) & 0xFFFFFFFF
return "%08x%08x" % (nr & 0x7FFFFFFF,nr2 & 0x7FFFFFFF)
request = cgi.FieldStorage()
checksum = request.getvalue('checksum') or ''
query_str = getenv('QUERY_STRING')
if _md5( SECRET + query_str ) == checksum:
# 从下文可以知道query_str == filename=py4h4sher&mode=download
mode = request.getvalue('mode') or ''
if mode == 'download':
filename = request.getvalue('filename') or ''
filename = os.path.basename( filename )
# 这里的filename会被覆盖,所以不可控
try:
print (open(filename).read())
except IOError as e:
print ('No such file or directory')
elif mode == 'eval':
bad_string = request.getvalue('filename') or ''
good_string = bad_string.encode('hex')
# 传入的filename要被hex编码再eval,也不可行
eval(good_string)
# 就只剩这一种方法了,要mode!= download or eval
else:
stage1 = request.getvalue('stage1') or ''
if m_hash(stage1) != '4141414141414141':
# m_hash(stage1)的返回值等于4141414141414141
gotoFail()
plaintext = getenv('HTTP_USER_AGENT')
stage2 = request.getvalue('stage2') or ''
if stage2 == plaintext:
# stage2的值不能等于user-agent
gotoFail()
if _pbkdf2(plaintext) != _pbkdf2(stage2):
# plaintext和stage2的pbkf2()返回值要相等
gotoFail()
stage3 = request.getvalue('stage3') or ''
stage3 = stage3[0]+stage3[1]+stage3[3]+stage3[5]
if _md5( stage3 ) != '90954349a0e42d8e4426a4672bde16b9':
# stage3的md5等于90954349a0e42d8e4426a4672bde16b9
gotoFail()
print ('Congrat! The flag is %s' % FLAG )
else:
checksum = _md5( SECRET + 'filename=py4h4sher&mode=download' )
print ("""
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
<meta name="author" content="orange@chroot.org">
<title> PY4H4SHER </title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<style>
/*
Inspired by http://dribbble.com/shots/890759-Ui-Kit-Metro/attachments/97174
*/
.out {
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
white-space: pre-wrap;
word-wrap: break-word; /* Internet Explorer 5.5+ */
background-color: white;
border: 0px;
}
.nav-row {
text-align: center;
}
.nav-row p {
padding: 5px;
}
.nav-row .col-md-2 {
background-color: #fff;
border: 1px solid #e0e1db;
border-right: none;
}
.nav-row .col-md-2:last-child {
border: 1px solid #e0e1db;
}
.nav-row .col-md-2:first-child {
border-radius: 5px 0 0 5px;
}
.nav-row .col-md-2:last-child {
border-radius: 0 5px 5px 0;
}
.nav-row .col-md-2:hover {
color: #e92d00;
cursor: pointer;
}
.nav-row .glyphicon {
padding-top: 15px;
font-size: 40px;
}
</style>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
</head>
<body>
<script>
var script_name = '/cgi-bin/py4h4sher';
function gohome(){
window.open( 'http://hitcon.org' );
}
function getflag(){
$.get( script_name ,
function(data){
$('.out').text('nothing to do :(');
});
}
function getsource(){
$.post( script_name + '?filename=py4h4sher&mode=download',
{'checksum': '%s'},
function(data){
$('.out').text(data);
});
}
</script>
<div class="container" style="margin-top:160px;">
<div class="row nav-row">
<div class="col-md-3">
</div>
<div class="col-md-2" onclick='gohome()'>
<span class="glyphicon glyphicon-home"></span>
<p> Go Home </p>
</div>
<div class="col-md-2" onclick='getflag()'>
<span class="glyphicon glyphicon-flag"></span>
<p> Get Flag </p>
</div>
<div class="col-md-2" onclick='getsource()'>
<span class="glyphicon glyphicon-cloud-download"></span>
<p> Get Source </p>
</div>
</div>
<div class='row nav-row'>
<pre class='out' style='padding-top:64px; '>
</pre>
</div>
</div>
</body>
</html>
""" % checksum)
看了一下源码,接下来要构造请求,需要满足以下要求:
checksum=af247ce6e8c70768eae27ec6feae34f6
query_str == filename=py4h4sher&mode=download
mode != download or eval
m_hash(stage1) == 4141414141414141
stage2 != user-agent
_pbkf2(user-agent) == _pbkf2(stage2)
_md5(stage3[0]+stage3[1]+stage3[3]+stage3[5]) == 90954349a0e42d8e4426a4672bde16b9
= =!前两个好说,第三个用参数污染,分别用GET和POST传输,最后一个暴力跑一下就可,但剩下的有点懵逼
贴个Write up,
知识点:
- HTTP Parameter Pollution
- 我手动测试了以下,如果get和post传输同名参数,后端会获取POST参数(可能不通用
后边这俩算是密码学的东西,第一个是passlib.mysql323
老版本的mysql加密哈希函数,第二个是PBKDF2-HMAC-SHA1
哈希函数,利用的是两个哈希函数的漏洞
- MySQL old_password hash collisions
- 这篇文章讲的比较清楚如何寻找第一个stage,我对加密算法不怎么了解,表达不是很好,感兴趣的话可以去那篇文章里看
- PBKDF2+HMAC hash collisions explained(这篇文章也讲的很清楚)
- 简单点说就是这个式子:
PBKDF2_HMAC_SHA1(chosen_password) == PBKDF2_HMAC_SHA1(HEX_TO_STRING(SHA1(chosen_password)))
- 简单点说就是这个式子:
最终的python代码
import requests
headers = {'User-Agent': 'chosen-prefix_hash_collisions_ftw_aaaaaaaaaaaaaaaaaaaaaaaafikpjor'}
base_url = 'http://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
params = {'checksum': 'af247ce6e8c70768eae27ec6feae34f6',
'mode': 'r3col',
'stage1': '*WqX%n~""8DpVv',
'stage2': "|BHwN@zatb0TT:5I3|7<",
'stage3': ['e', 'n', 'x', 'i', 'x', 'gma'],
}
res = requests.post(url=base_url + "/cgi-bin/py4h4sher?filename=py4h4sher&mode=download", data=params,
headers=headers)
print(res.text)
理解有限,如果有误,还请各位师傅指正