bambi7-service-catchbox

Simple Web-based file storage A/D service for BambiCTF7 in 2022
git clone https://git.sinitax.com/sinitax/bambi7-service-catchbox
Log | Files | Refs | README | sfeed.txt

index.php (14182B)


      1<?php
      2
      3$sites = array(
      4	"home"     =>  array ( "name" => "Home"  ),
      5	"login"    =>  array ( "name" => "Login" ),
      6	"register" =>  array ( "name" => "Register" ),
      7	"files"    =>  array ( "name" => "Files" ),
      8	"users"    =>  array ( "name" => "Users" ),
      9	"report"   =>  array ( "name" => "Contact" ),
     10	"about"    =>  array ( "name" => "About" )
     11);
     12
     13$db = null;
     14$login = "";
     15
     16$wrotehead = false;
     17$head = <<<EOF
     18<!DOCTYPE html>
     19<html>
     20<head>
     21	<title>Catchbox</title>
     22	<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
     23	<link rel="stylesheet" href="/static/style.css">
     24</head>
     25<body>
     26EOF;
     27
     28srand(time());
     29
     30function load() {
     31	global $db;
     32	if ($db === null) {
     33		/* https://phiresky.github.io/blog/2020/sqlite-performance-tuning/ */
     34		$db = new SQLite3("db.sqlite");
     35		$db->busyTimeout(15000);
     36		$db->exec("PRAGMA journal_mode = WAL");
     37		$db->exec("PRAGMA synchronous = normal");
     38		$db->exec("PRAGMA temp_storage = memory");
     39		$db->exec("PRAGMA mmap_size = 30000000000");
     40		$db->exec("PRAGMA page_size = 32768");
     41		/* for ON DELETE CASCADE */
     42		$db->exec("PRAGMA foreign_keys = ON");
     43	}
     44	return $db;
     45}
     46
     47function writehead() {
     48	global $head, $wrotehead;
     49	if (!$wrotehead) {
     50		echo $head;
     51	}
     52	$wrotehead = true;
     53}
     54
     55function banner($msg) {
     56	http_response_code(400);
     57	writehead();
     58	echo "<div class=error>" . $msg . "</div>";
     59}
     60
     61function alphok($text) {
     62	return preg_match("#^[a-zA-Z0-9\.\-_äöüÄÖÜ]*$#", $text);
     63}
     64
     65function quit() {
     66	global $db;
     67	if ($db !== null)
     68		$db->close();
     69	exit();
     70}
     71
     72function redirect($url) {
     73	header("HTTP/1.1 301 Moved Permanently");
     74	header("Location: $url");
     75	quit();
     76}
     77
     78function serv_file($path) {
     79	$path = realpath($path);
     80	if ($path === false || strpos($path, "/service/files/") !== 0
     81			&& strpos($path, "/service/reports/") !== 0) {
     82		header("HTTP/1.1 404 Not Found");
     83		quit();
     84	}
     85
     86	$mime = mime_content_type($path);
     87	if ($mime !== false) {
     88		header("Content-Type: " . $mime);
     89	}
     90	echo file_get_contents($path);
     91	quit();
     92}
     93
     94function serv_post() {
     95	global $db, $login;
     96	if ($_POST["action"] == "register") {
     97		if (!isset($_POST["username"]) || !isset($_POST["password"])) {
     98			banner("Missing username / password");
     99			return "home";
    100		}
    101
    102		if (!alphok($_POST["username"]) || strlen($_POST["username"]) > 100) {
    103			banner("Invalid username");
    104			return "home";
    105		}
    106
    107		if (strlen($_POST["password"]) > 100) {
    108			banner("Invalid password");
    109			return "home";
    110		}
    111
    112		$db = load();
    113		$q = $db->prepare("SELECT user FROM users WHERE user = :user");
    114		$q->bindValue(":user", $_POST["username"]);
    115		$res = $q->execute();
    116		if ($res !== false && $res->fetchArray() !== false) {
    117			$q->close();
    118			banner("User already exists");
    119			return "home";
    120		}
    121		$q->close();
    122
    123		$auth = md5($_POST["username"] . $_POST["password"]);
    124		$q = $db->prepare("INSERT INTO users (user, pass, creat, auth) "
    125			. "VALUES (:user, :pass, :creat, :auth)");
    126		$q->bindValue(":user", $_POST["username"], SQLITE3_TEXT);
    127		$q->bindValue(":pass", $_POST["password"], SQLITE3_TEXT);
    128		$q->bindValue(":creat", time(), SQLITE3_INTEGER);
    129		$q->bindValue(":auth", $auth, SQLITE3_TEXT);
    130		$res = $q->execute();
    131		if ($res === false) {
    132			$q->close();
    133			banner("Failed to insert user: " . $db->lastErrorMsg());
    134			return "home";
    135		}
    136		$q->close();
    137
    138		$login = $_POST["username"];
    139		setcookie("session", $auth);
    140
    141		return "files";
    142	} else if ($_POST["action"] === "login") {
    143		if (!isset($_POST["username"]) || !isset($_POST["password"])) {
    144			banner("Missing username / password");
    145			return "home";
    146		}
    147
    148		$db = load();
    149		$q = $db->prepare("SELECT auth FROM users WHERE user = :user AND pass = :pass");
    150		$q->bindValue(":user", $_POST["username"], SQLITE3_TEXT);
    151		$q->bindValue(":pass", $_POST["password"], SQLITE3_TEXT);
    152		$res = $q->execute();
    153		if ($res === false || ($row = $res->fetchArray()) === false) {
    154			$q->close();
    155			banner("Invalid credentials");
    156			return "home";
    157		}
    158		$auth = $row[0];
    159		$q->close();
    160
    161		$login = $_POST["username"];
    162		setcookie("session", $auth);
    163
    164		return "files";
    165	} else if ($_POST["action"] === "upload") {
    166		if (!isset($_COOKIE["session"]))  {
    167			banner("Not authenticated");
    168			return "files";
    169		}
    170
    171		if (!isset($_POST["filename"]) || !isset($_POST["content"])) {
    172			banner("Missing content or filename");
    173			return "files";
    174		}
    175
    176		if (strlen($_POST["filename"]) > 100) {
    177			banner("Invalid filename");
    178			return "files";
    179		}
    180
    181		if (strlen($_POST["content"]) > 1024) {
    182			banner("File too large");
    183			return "files";
    184		}
    185
    186		$db = load();
    187		$q = $db->prepare("SELECT uid, user from users WHERE auth = :auth");
    188		$q->bindValue(":auth", $_COOKIE["session"], SQLITE3_TEXT);
    189		$res = $q->execute();
    190		if ($res === false || ($row = $res->fetchArray()) === false) {
    191			$q->close();
    192			banner("Invalid session");
    193			setcookie("session", "", 1);
    194			return "files";
    195		}
    196		$uid = $row[0];
    197		$user = $row[1];
    198		$login = $user;
    199		$q->close();
    200
    201		$parts = explode("/", $_POST["filename"]);
    202		$filename = end($parts);
    203		foreach ($parts as $part) {
    204			if (strpos($part, "..") != false) {
    205				banner("Invalid filename");
    206				return "files";
    207			}
    208		}
    209
    210		$dir = md5($user . $filename . strval(rand()));
    211		$dirpath = "files/" . $dir;
    212		if (is_dir($dirpath) || mkdir($dirpath) === false) {
    213			banner("File directory already exists");
    214			return "files";
    215		}
    216
    217		$filepath = $dirpath . "/" . $filename;
    218		if (is_file($filepath)) {
    219			banner("File already exists");
    220			return "files";
    221		}
    222
    223		$f = fopen($filepath, "w+");
    224		if ($f === false) {
    225			banner("File create failed");
    226			return "files";
    227		}
    228		fwrite($f, $_POST["content"]);
    229		fclose($f);
    230
    231		$q = $db->prepare("INSERT INTO files (uid, file, dir, creat) "
    232			. "VALUES (:uid, :file, :dir, :creat)");
    233		$q->bindValue(":uid", $uid, SQLITE3_INTEGER);
    234		$q->bindValue(":file", $_POST["filename"], SQLITE3_TEXT);
    235		$q->bindValue(":dir", $dir, SQLITE3_TEXT);
    236		$q->bindValue(":creat", time(), SQLITE3_INTEGER);
    237		$res = $q->execute();
    238		if ($res === false) {
    239			$q->close();
    240			banner("Failed to insert file: " . $db->lastErrorMsg());
    241			return "files";
    242		}
    243		$q->close();
    244
    245		return "files";
    246	} else if ($_POST["action"] == "report") {
    247		if (!isset($_COOKIE["session"]))  {
    248			banner("Not authenticated");
    249			return "files";
    250		}
    251
    252		if (!isset($_POST["content"])) {
    253			banner("Missing content or filename");
    254			return "files";
    255		}
    256
    257		if (strlen($_POST["content"]) > 1024) {
    258			banner("Report too long");
    259			return "files";
    260		}
    261
    262		$db = load();
    263		$q = $db->prepare("SELECT uid, user from users WHERE auth = :auth");
    264		$q->bindValue(":auth", $_COOKIE["session"], SQLITE3_TEXT);
    265		$res = $q->execute();
    266		if ($res === false || ($row = $res->fetchArray()) === false) {
    267			$q->close();
    268			banner("Invalid session");
    269			setcookie("session", "", 1);
    270			return "report";
    271		}
    272		$uid = $row[0];
    273		$user = $row[1];
    274		$q->close();
    275
    276		$login = $user;
    277		$file = md5($user);
    278
    279		$q = $db->prepare("INSERT INTO reports (uid, file, creat) "
    280			. "VALUES (:uid, :file, :creat)");
    281		$q->bindValue(":uid", $uid, SQLITE3_INTEGER);
    282		$q->bindValue(":file", $file, SQLITE3_TEXT);
    283		$q->bindValue(":creat", time(), SQLITE3_INTEGER);
    284		$res = $q->execute();
    285		if ($res === false) {
    286			$q->close();
    287			banner("Failed to insert report: " . $db->lastErrorMsg());
    288			return "files";
    289		}
    290		$q->close();
    291
    292		$filepath = "reports/" . $file;
    293		if (is_file($filepath)) {
    294			banner("Report already exists");
    295			return "report";
    296		}
    297
    298		$f = fopen($filepath, "w+");
    299		if ($f === false) {
    300			banner("Report create failed");
    301			return "files";
    302		}
    303		fwrite($f, $_POST["content"]);
    304		fclose($f);
    305
    306		return "report";
    307	}
    308
    309	return "home";
    310}
    311
    312function serv() {
    313	global $db, $login;
    314	if ($_SERVER["REQUEST_METHOD"] === "POST") {
    315		$site = serv_post();
    316	} else if (isset($_GET["f"])) {
    317		if (!isset($_COOKIE["session"]))  {
    318			banner("Not authenticated");
    319			return "files";
    320		}
    321
    322		$db = load();
    323		$q = $db->prepare("SELECT uid from users WHERE auth = :auth");
    324		$q->bindValue(":auth", $_COOKIE["session"], SQLITE3_TEXT);
    325		$res = $q->execute();
    326		if ($res === false || ($row = $res->fetchArray()) === false) {
    327			$q->close();
    328			banner("Invalid session");
    329			setcookie("session", "", 1);
    330			return "files";
    331		}
    332		$uid = $row[0];
    333		$q->close();
    334
    335		$q = $db->prepare("SELECT dir, file from files "
    336			. "WHERE uid = :uid and file = :file");
    337		$q->bindValue(":uid", $uid, SQLITE3_INTEGER);
    338		$q->bindValue(":file", $_GET["f"], SQLITE3_TEXT);
    339		$res = $q->execute();
    340		if ($res === false || ($row = $res->fetchArray()) === false) {
    341			$q->close();
    342			banner("No such file");
    343			return "files";
    344		}
    345		$path = "files/" . $row[0] . "/" . $row[1];
    346		$q->close();
    347
    348		serv_file($path);
    349	} else if (isset($_GET["r"])) {
    350		if (!isset($_COOKIE["session"]))  {
    351			banner("Not authenticated");
    352			return "files";
    353		}
    354
    355		$db = load();
    356		$q = $db->prepare("SELECT file from reports WHERE "
    357			. "uid = (SELECT uid FROM users WHERE auth = :auth)");
    358		$q->bindValue(":auth", $_COOKIE["session"], SQLITE3_TEXT);
    359		$res = $q->execute();
    360		if ($res === false || ($row = $res->fetchArray()) === false) {
    361			$q->close();
    362			banner("Invalid session");
    363			setcookie("session", "", 1);
    364			return "files";
    365		}
    366		$path = "reports/" . $row[0];
    367		$q->close();
    368
    369		serv_file($path);
    370	} else {
    371		if (isset($_GET["q"]))
    372			$site = $_GET['q'];
    373		else
    374			$site = "home";
    375	}
    376
    377	if ($site == "logout") {
    378		setcookie("session", "", 1);
    379		$login = "";
    380		return "home";
    381	}
    382
    383	return $site;
    384}
    385
    386$site = serv();
    387
    388if (isset($_COOKIE["session"]) && $login === "") {
    389	$db = load();
    390	$q = $db->prepare("SELECT user FROM users WHERE auth = :auth");
    391	$q->bindValue(":auth", $_COOKIE["session"], SQLITE3_TEXT);
    392	$res = $q->execute();
    393	if ($res === false || ($row = $res->fetchArray()) === false) {
    394		$q->close();
    395		banner("Invalid session");
    396		setcookie("session", "", 1);
    397		return $site;
    398	}
    399	$login = $row[0];
    400	$q->close();
    401}
    402
    403writehead();
    404?>
    405	<div class="navbar">
    406		<img src="/static/logo.png"></img>
    407		<ul>
    408<?php
    409
    410foreach ($sites as $qname => $info) {
    411	if ($site == $qname) {
    412		echo '<li><a class="active" href="/?q=' . $qname . '">' . $info["name"] . '</a></li>';
    413	} else {
    414		if ($login != "" && $qname != "login" && $qname != "register"
    415				|| $login == "" && $qname != "files" && $qname != "report") {
    416			echo '<li><a href="/?q=' . $qname . '">' . $info["name"] . '</a></li>';
    417		}
    418	}
    419}
    420
    421?>
    422		</ul>
    423	</div>
    424	<div class="main">
    425		<div class="login">
    426<?php
    427
    428if ($login != "") {
    429	echo '<a href="/?q=logout">logged in (' . $login . ')</a>';
    430} else {
    431	echo '<a href="/?q=login">log in</a>';
    432}
    433
    434?>
    435		</div>
    436<?php
    437if ($site == "home") {
    438	echo '
    439		<div class="text">
    440			<h1>Welcome to Catchbox&#8482</h1>
    441			<p>Host your files with us! Register now and get 5GB for free!&#185</p>
    442		<div>
    443		<div class="footer">
    444			&#185 Terms and conditions apply.
    445		</div>';
    446} else if ($site == "login") {
    447	echo '
    448		<div class="text">
    449			<form action="index.php" method="post" class="login-form">
    450				<h2>Login:</h2>
    451				<input class="txtinput" name="username" type="text" placeholder="username"></input>
    452				<input class="txtinput" name="password" type="password" placeholder="password"></input>
    453				<input type="hidden" name="action" value="login">
    454				<input type="submit">
    455				<a class=hint href=/?q=register>Need an account?</a>
    456			</form>
    457		<div>';
    458} else if ($site == "register") {
    459	echo '
    460		<div class="text">
    461			<form action="index.php" method="post" class="login-form">
    462				<h2>Register:</h2>
    463				<input class="txtinput" name="username" type="text" placeholder="username"></input>
    464				<input class="txtinput" name="password" type="password" placeholder="password"></input>
    465				<input type="hidden" name="action" value="register">
    466				<input type="submit">
    467			</form>
    468		<div>';
    469} else if ($site == "files") {
    470	echo '
    471		<div class="text">
    472			<h2>Your hosted files:</h2>
    473			<ul class="mslist filelist">';
    474	$db = load();
    475	$q = $db->prepare("SELECT file, dir FROM files WHERE "
    476		. "uid = (SELECT uid FROM users WHERE user = :user)");
    477	$q->bindValue(":user", $login, SQLITE3_TEXT);
    478	$res = $q->execute();
    479	while (($row = $res->fetchArray())) {
    480		$pub = "/uploads/" . $row[1] . "/" . $row[0];
    481		$priv = "/index.php?f=" . $row[0];
    482		echo '
    483				<li>
    484					<p class="front">
    485						<a class="mfile" href="' . $priv . '">' . $row[0] . '</a>
    486					</p>
    487					<p class="back">
    488						<a class="textref" href="' . $pub . '">share</a>
    489					</p>
    490				</li>';
    491	}
    492	$q->close();
    493	echo '
    494			</ul>
    495			<form action="index.php" method="post" class="upload-form">
    496				<h2>Upload a file:</h2>
    497				<input type=text name="filename" placeholder="name"></input><br>
    498				<input type=text name="content" placeholder="content"></input><br>
    499				<input type=hidden name="action" value="upload">
    500				<input type=submit>
    501			</form>
    502		<div>';
    503} else if ($site == "users") {
    504	echo '
    505		<div class="text">
    506			<h2>Registered users:</h2>
    507			<ul class="mslist userlist">';
    508	$db = load();
    509	$res = $db->query("SELECT user, creat FROM users ORDER BY creat DESC");
    510	while (($row = $res->fetchArray())) {
    511		$date = date("Y-m-d H:i:s", $row[1]);
    512		echo '<li><p class="front">' . $row[0] . '</p>';
    513		echo '<p class="back">' . $date . '</p></li>';
    514	}
    515	echo '
    516			</ul>
    517		<div>';
    518} else if ($site == "report") {
    519	echo '
    520		<div class="text">';
    521
    522	$db = load();
    523	$q = $db->prepare("SELECT file FROM reports WHERE "
    524		. "uid = (SELECT uid FROM users WHERE user = :user)");
    525	$q->bindValue(":user", $login, SQLITE3_TEXT);
    526	$res = $q->execute();
    527	if ($res === false || ($row = $res->fetchArray()) === false) {
    528		echo'
    529			<h2>We would love to hear from you!</h2>
    530			<form action="index.php" method="post" class="upload-form">
    531				<h2>Submit feedback:</h2>
    532				<input type=text name="content"></input><br>
    533				<input type=hidden name="action" value="report">
    534				<input type=submit>
    535			</form>';
    536	} else {
    537		echo'
    538			<h2>Thank you for your feedback.</h2>
    539			You can view your feedback <a class=textref href="/?r">here</a>';
    540	}
    541	$q->close();
    542	echo '
    543		<div>';
    544} else if ($site == "about") {
    545	echo '
    546		<div class="text">
    547			<h1>We value our employees:</h1>
    548			Become a member of our team today!
    549			<div class="employee-pics">
    550				<img width="100%" src="/static/work.jpg"/>
    551				<img width="100%" src="/static/work2.jpg"/>
    552			</div>
    553		</div>
    554';
    555}
    556?>
    557			</div>
    558	</body>
    559</html>
    560
    561<?php
    562	quit();
    563?>