From e89de763d543f31e2e8b55d43d2bb8a1baa40d93 Mon Sep 17 00:00:00 2001 From: Floorb <132411956+Neetpone@users.noreply.github.com> Date: Mon, 14 Mar 2022 15:43:01 -0400 Subject: [PATCH] CSRF stuff I guess --- .htaccess | 52 ------------ README.md | 2 +- includes/Helpers/SessionHelper.php | 2 +- includes/Models/Paste.php | 2 - includes/captcha.php | 6 +- includes/common.php | 30 +++++++ includes/functions.php | 9 +-- index.php | 7 ++ js/data_tables.js | 4 + js/user_profile.js | 21 +++++ pages.php | 9 ++- paste.php | 27 ++++++- profile.php | 7 +- report.php | 15 ---- theme/bulma/common.php | 4 + theme/bulma/event.php | 125 +---------------------------- theme/bulma/main.php | 5 +- theme/bulma/profile.php | 17 ++-- theme/bulma/user_profile.php | 14 +++- theme/bulma/view.php | 33 ++++---- user.php | 19 +---- 21 files changed, 148 insertions(+), 262 deletions(-) delete mode 100644 .htaccess delete mode 100644 report.php diff --git a/.htaccess b/.htaccess deleted file mode 100644 index 39c72ad..0000000 --- a/.htaccess +++ /dev/null @@ -1,52 +0,0 @@ -Options +FollowSymLinks -RewriteEngine on - - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule ^page/([a-zA-Z0-9]+)/? pages.php?page=$1 [L] - RewriteRule ^archive archive.php [L] - RewriteRule ^discover discover.php [L] - RewriteRule ^profile profile.php [L] - RewriteRule ^user/([^/]+)/?$ user.php?user=$1 [L] - RewriteRule ^user/([^/]+)/([^/]+)/?$ user.php?user=$1&q=$2 [L] - RewriteRule ^contact contact.php [L] - RewriteRule ^download/(.*)$ paste.php?download&id=$1 [L] - RewriteRule ^raw/(.*)$ paste.php?raw&id=$1 [L] - RewriteRule ^embed/(.*)$ paste.php?embed&id=$1 [L] - RewriteRule ^report report.php [L] - RewriteRule ^event event.php [L] -Options +FollowSymLinks -RewriteBase / -RewriteCond %{HTTP_USER_AGENT} "=Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0" [OR] -RewriteCond %{HTTP_USER_AGENT} "=Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0" [OR] -RewriteCond %{HTTP_USER_AGENT} "=Mozilla/5.0 (Windows NT 6.2; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0" [OR] -RewriteCond %{HTTP_USER_AGENT} "=Mozilla/5.0 (Windows NT 6.2; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0" [OR] -RewriteCond %{HTTP_USER_AGENT} "=Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36" [OR] -RewriteCond %{HTTP_USER_AGENT} "=Mozilla/5.0 (Windows NT 6.1; rv:24.0) Gecko/20100101 Firefox/24.0" [OR] -RewriteCond %{HTTP_USER_AGENT} "=Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0" [OR] -RewriteCond %{HTTP_USER_AGENT} Mb2345Browser|LieBaoFast|zh-CN|MicroMessenger|zh_CN|Kinza|Datanyze|serpstatbot|spaziodati|OPPO\sA33|AspiegelBot|aspiegel|PetalBot|SemrushBot/7~bl [NC] -RewriteRule ^ - [F,L] - - - - - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule ^(.*)$ paste.php?id=$1 [L] - - -# compress text, html, javascript, css, xml: -AddOutputFilterByType DEFLATE text/plain -AddOutputFilterByType DEFLATE text/html -AddOutputFilterByType DEFLATE text/xml -AddOutputFilterByType DEFLATE text/css -AddOutputFilterByType DEFLATE application/xml -AddOutputFilterByType DEFLATE application/xhtml+xml -AddOutputFilterByType DEFLATE application/rss+xml -AddOutputFilterByType DEFLATE application/javascript -AddOutputFilterByType DEFLATE application/x-javascript - -# Or, compress certain file types by extension: - -SetOutputFilter DEFLATE - \ No newline at end of file diff --git a/README.md b/README.md index 8f2e46c..b155986 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,4 @@ punishedponepaste # Building the JS When you change the JS, you need to rebuild it. `assets/bundle.js` is used in dev, `assets/bundle.min.js` is used in production. -You need Yarn (version 1, not version 2 - 2 may work but I haven't tried it.) After that, whenever you change anything under `js/`, you need to run `yarn rollup --config`. Good luck! \ No newline at end of file +You need Yarn (version 1, not version 2 - 2 may work, but I haven't tried it.) After that, whenever you change anything under `js/`, you need to run `yarn rollup --config`. Good luck! \ No newline at end of file diff --git a/includes/Helpers/SessionHelper.php b/includes/Helpers/SessionHelper.php index 0b45518..a13970f 100644 --- a/includes/Helpers/SessionHelper.php +++ b/includes/Helpers/SessionHelper.php @@ -7,6 +7,7 @@ use PonePaste\Models\UserSession; class SessionHelper { public const REMEMBER_TOKEN_COOKIE = '_ponepaste_token'; + public const CSRF_TOKEN_KEY = 'csrf_token'; public static function currentUser() { $session_user = SessionHelper::currentUserFromPhpSession(); @@ -60,7 +61,6 @@ class SessionHelper { return null; } - return User::find(intval($_SESSION['user_id'])); } } \ No newline at end of file diff --git a/includes/Models/Paste.php b/includes/Models/Paste.php index df2a1b9..4241dc3 100644 --- a/includes/Models/Paste.php +++ b/includes/Models/Paste.php @@ -33,8 +33,6 @@ class Paste extends Model { $this->tags()->attach($tag); } - // FIXME: We need to get rid of tagsys. - $this->tagsys = implode(',', $tags); $this->save(); } diff --git a/includes/captcha.php b/includes/captcha.php index 032b6c2..bc1775c 100644 --- a/includes/captcha.php +++ b/includes/captcha.php @@ -89,7 +89,7 @@ function captcha($color, $mode, $mul, $allowed) : array { } // Generate HTML for image src - $image_src = substr(__FILE__, strlen(realpath($_SERVER['DOCUMENT_ROOT']))) . '?_CAPTCHA&t=' . urlencode(microtime()); + $image_src = substr(__FILE__, strlen(realpath($_SERVER['DOCUMENT_ROOT']))) . '?_CAPTCHA&_R=' . urlencode(rand()); $image_src = '/' . ltrim(preg_replace('/\\\\/', '/', $image_src), '/'); $_SESSION['_CAPTCHA']['config'] = serialize($captcha_config); @@ -125,7 +125,6 @@ if (!function_exists('hex2rgb')) { // Draw the image if (isset($_GET['_CAPTCHA'])) { - session_start(); $captcha_config = unserialize(@$_SESSION['_CAPTCHA']['config']); @@ -178,5 +177,4 @@ if (isset($_GET['_CAPTCHA'])) { // Output image header("Content-type: image/png"); imagepng($captcha); - -} \ No newline at end of file +} diff --git a/includes/common.php b/includes/common.php index 86b210c..508898d 100644 --- a/includes/common.php +++ b/includes/common.php @@ -5,6 +5,7 @@ if (!defined('IN_PONEPASTE')) { require_once(__DIR__ . '/../vendor/autoload.php'); require_once(__DIR__ . '/config.php'); require_once(__DIR__ . '/functions.php'); +require_once(__DIR__ . '/passwords.php'); require_once(__DIR__ . '/DatabaseHandle.class.php'); use Illuminate\Database\Capsule\Manager as Capsule; @@ -154,6 +155,33 @@ function updatePageViews() : void { } } +function setupCsrfToken() : string { + if (isset($_SESSION[SessionHelper::CSRF_TOKEN_KEY])) { + return $_SESSION[SessionHelper::CSRF_TOKEN_KEY]; + } + + $token = pp_random_token(); + $_SESSION[SessionHelper::CSRF_TOKEN_KEY] = $token; + + return $token; +} + +function verifyCsrfToken($token = null) : bool { + if ($token === null) { + $token = $_POST[SessionHelper::CSRF_TOKEN_KEY]; + } + + if (empty($token) || empty($_SESSION[SessionHelper::CSRF_TOKEN_KEY])) { + return false; + } + + $success = hash_equals($_SESSION[SessionHelper::CSRF_TOKEN_KEY], $token); + + unset($_SESSION[SessionHelper::CSRF_TOKEN_KEY]); + + return $success; +} + session_start(); /* Set up the database and Eloquent ORM */ @@ -215,6 +243,8 @@ $total_unique_views = PageView::select('tvisit')->orderBy('id', 'desc')->first() $current_user = SessionHelper::currentUser(); +//SessionHelper::setupCsrfToken(); + $script_bundles = []; /* Security headers */ diff --git a/includes/functions.php b/includes/functions.php index 854d845..9a2db76 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -55,13 +55,6 @@ function tagsToHtmlUser(string | array | Collection $tags, $profile_username) : return $output; } -function getevent($conn, $event_name, $count) { - $query = $conn->prepare("SELECT id, visible, title, date, now_time, views, member FROM pastes WHERE visible='1' AND tagsys LIKE '%?%' - ORDER BY RAND () LIMIT 0, ?"); - $query->execute([$event_name, $count]); - return $query->fetchAll(); -} - function linkify($value, $protocols = array('http', 'mail'), array $attributes = array()) { // Link attributes $attr = ''; @@ -150,7 +143,7 @@ function truncate(string $input, int $maxWords, int $maxChars) : string { return $result . ($input == $result ? '' : '[...]'); } -function embedView($paste_id, $p_title, $content, $p_code, $title, $baseurl, $lang) { +function embedView($paste_id, $p_title, $content, $title) { $stats = false; if ($content) { // Build the output diff --git a/index.php b/index.php index 4cff16a..a398311 100644 --- a/index.php +++ b/index.php @@ -75,6 +75,11 @@ updatePageViews(); // POST Handler if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (!verifyCsrfToken()) { + $error = 'Incorrect CSRF token (do you have cookies enabled?)'; + goto OutPut; + } + $error = validatePasteFields(); if ($error !== null) { @@ -172,6 +177,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } + OutPut: +$csrf_token = setupCsrfToken(); $page_template = 'main'; require_once('theme/' . $default_theme . '/common.php'); diff --git a/js/data_tables.js b/js/data_tables.js index f5a2f17..9f88b0c 100644 --- a/js/data_tables.js +++ b/js/data_tables.js @@ -224,6 +224,10 @@ class DataTable { } const dumbFilterCallback = (datum, query) => { + if (!query) { + return true; + } + if (datum.title.indexOf(query) !== -1) { return true; } diff --git a/js/user_profile.js b/js/user_profile.js index aa8a0f7..3134047 100644 --- a/js/user_profile.js +++ b/js/user_profile.js @@ -2,6 +2,16 @@ import { escape, whenReady } from './dom'; import { DataTable, dumbFilterCallback } from './data_tables'; import { globalSetup } from './main'; +const getUserInfo = () => { + const elem = document.getElementById('js-data-holder'); + + if (!elem) { + return { userId: null, csrfToken: null }; + } + + return { userId: elem.dataset.userId, csrfToken: elem.dataset.csrfToken }; +}; + const parsePasteInfo = (elem) => { if (!elem.dataset.pasteInfo) { return null; @@ -40,12 +50,23 @@ whenReady(() => { `; }).join(''); + const userData = getUserInfo(); + + const deleteElem = true ? ` +
+ + + +
+ ` : ''; + return ` ${escape(rowData.title)} ${rowData.created_at} ${rowData.visibility} ${rowData.views || 0} ${tags} + ${deleteElem} `; }, filterCallback: dumbFilterCallback, diff --git a/pages.php b/pages.php index 41fff06..26469b2 100644 --- a/pages.php +++ b/pages.php @@ -7,13 +7,16 @@ use PonePaste\Models\Page; updatePageViews(); +$page_title = 'Page not found'; + if (isset($_GET['page'])) { $page = Page::select('page_title', 'page_content', 'last_date') ->where('page_name', $_GET['page']) ->first(); - $page_title = $page->page_title; -} else { - $page_title = 'Page not found'; + + if (isset($page)) { + $page_title = $page->page_title; + } } $page_template = 'pages'; diff --git a/paste.php b/paste.php index 6b0f114..d904732 100644 --- a/paste.php +++ b/paste.php @@ -2,7 +2,6 @@ define('IN_PONEPASTE', 1); require_once('includes/common.php'); require_once('includes/functions.php'); -require_once('includes/passwords.php'); use Highlight\Highlighter; use PonePaste\Models\Paste; @@ -55,6 +54,13 @@ if (!$paste) { goto Not_Valid_Paste; } +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (!verifyCsrfToken()) { + $notfound = 'Invalid CSRF token (do you have cookies enabled?)'; + goto Not_Valid_Paste; + } +} + $paste_owner_id = $paste->user->id; $paste_title = $paste->title; $paste_code = $paste->code; @@ -72,7 +78,6 @@ $fav_count = $paste->favouriters()->count(); 'tags' => getPasteTags($conn, $paste_id) ];*/ -//$p_member = $row['member']; $p_content = $paste->content; $p_visible = $paste->visible; $p_expiry = $paste->expiry; @@ -80,7 +85,6 @@ $p_password = $paste->password; $p_encrypt = (bool) $paste->encrypt; $paste_is_favourited = $current_user !== null && $current_user->favourites->where('paste_id', $paste->id)->count() === 1; - $is_private = $p_visible === '2'; if ($is_private && (!$current_user || $current_user->id !== $paste_owner_id)) { @@ -88,6 +92,19 @@ if ($is_private && (!$current_user || $current_user->id !== $paste_owner_id)) { goto Not_Valid_Paste; } +/* Paste deletion */ +if (isset($_POST['delete'])) { + if (!$current_user || ($current_user->id !== $paste_owner_id)) { + $notfound = 'You cannot delete someone else\'s paste!'; + goto Not_Valid_Paste; + } + + $paste->delete(); + flashSuccess('Paste deleted.'); + header('Location: ' . urlForMember($current_user)); + die(); +} + /* Verify paste password */ $password_required = $p_password !== null && $p_password !== 'NONE'; $password_valid = true; @@ -191,7 +208,7 @@ if ($paste_code === "pastedown") { // Embed view after highlighting is applied so that $p_code is syntax highlighted as it should be. if (isset($_GET['embed'])) { - embedView($paste_id, $paste_title, $p_content, $paste_code, $title, $baseurl, $lang); + embedView($paste_id, $paste_title, $p_content, $title); exit(); } @@ -230,5 +247,7 @@ if ($is_private || $notfound || !$password_valid) { // Display errors $page_template = 'errors'; } + +$csrf_token = setupCsrfToken(); require_once('theme/' . $default_theme . '/common.php'); diff --git a/profile.php b/profile.php index ef6b505..7041bfc 100644 --- a/profile.php +++ b/profile.php @@ -6,7 +6,6 @@ require_once('includes/passwords.php'); use PonePaste\Models\Paste; -// Check if already logged in if ($current_user === null) { header("Location: ./login.php"); die(); @@ -19,7 +18,9 @@ $user_ip = $current_user->ip; $user_password = $current_user->password; if ($_SERVER['REQUEST_METHOD'] == 'POST') { - if (isset($_POST['cpassword'])) { + if (!verifyCsrfToken()) { + $error = 'Invalid CSRF token (do you have cookies enabled?)'; + } else if (isset($_POST['cpassword'])) { $user_new_full = trim(htmlspecialchars($_POST['full'])); $user_old_pass = $_POST['old_password']; if (pp_password_verify($user_old_pass, $user_password)) { @@ -40,8 +41,8 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { updatePageViews(); $total_user_pastes = Paste::where('user_id', $current_user->id)->count(); +$csrf_token = setupCsrfToken(); -// Theme $page_template = 'profile'; $page_title = 'My Profile'; require_once('theme/' . $default_theme . '/common.php'); diff --git a/report.php b/report.php deleted file mode 100644 index a2230f5..0000000 --- a/report.php +++ /dev/null @@ -1,15 +0,0 @@ -username : 'Guest'; -$p_pastereport = $_POST['reppasteid']; -$p_reasonrep = preg_replace("/[^0-9]/", "", $p_reasonrep); - -$conn->prepare('INSERT INTO user_reports (m_report, p_report, t_report, rep_reason) VALUES (?, ?, NOW(), ?)') - ->execute([$p_memreport, $p_pastereport, $p_reasonrep]); -$repmes = "Paste has been reported."; - diff --git a/theme/bulma/common.php b/theme/bulma/common.php index e364de7..2ce6f91 100644 --- a/theme/bulma/common.php +++ b/theme/bulma/common.php @@ -125,6 +125,10 @@ $flashes = getFlashes(); + + + + diff --git a/theme/bulma/main.php b/theme/bulma/main.php index 63319a1..719a6b5 100644 --- a/theme/bulma/main.php +++ b/theme/bulma/main.php @@ -298,12 +298,15 @@
CAPTCHA + placeholder="Enter the CAPTCHA" />

and press "Enter"

+ + + diff --git a/theme/bulma/profile.php b/theme/bulma/profile.php index 74c20c7..22ed474 100644 --- a/theme/bulma/profile.php +++ b/theme/bulma/profile.php @@ -4,7 +4,9 @@

Total Pastes:

-

username) . '" target="_self">My Pastes'; ?>

+

+ My Pastes +


My Profile

-
+
+ style="cursor:not-allowed;" placeholder="New key generated here"> @@ -43,9 +45,9 @@
- + placeholder="username); ?>"> @@ -84,6 +86,9 @@
+ + +
@@ -94,4 +99,4 @@
- \ No newline at end of file + diff --git a/theme/bulma/user_profile.php b/theme/bulma/user_profile.php index 6e821d8..fdf1750 100644 --- a/theme/bulma/user_profile.php +++ b/theme/bulma/user_profile.php @@ -1,4 +1,6 @@ '[ProbablyAutistic] Have more than Fifty pastes', 25 => '[Writefag] Have Twenty Five or more pastes', @@ -127,14 +129,22 @@ ['visibility' => $p_visible] ); ?> - + visible == Paste::VISIBILITY_PUBLIC): ?> format('d F Y') ?> views ?> tags, $profile_username); ?> - + + + + + + +
+ + diff --git a/theme/bulma/view.php b/theme/bulma/view.php index 0178eef..9956824 100644 --- a/theme/bulma/view.php +++ b/theme/bulma/view.php @@ -64,7 +64,8 @@ $selectedloader = "$bg[$i]"; // set variable equal to which random filename was
-
+

Reporting is currently non-functional. Please email admin ( a t ) ponepaste (.) org if this is a serious violation.

+
-
+
@@ -128,6 +129,9 @@ $selectedloader = "$bg[$i]"; // set variable equal to which random filename was
+ + +
@@ -349,26 +353,17 @@ $selectedloader = "$bg[$i]"; // set variable equal to which random filename was
- > + type="checkbox" disabled="disabled" checked="checked" /> id == $paste['user_id']) { + if ($current_user->id === $paste['user_id']) { ?> + + + id)->sum('faves'); $total_pfav = $profile_info->favourites->count(); $total_yfav = $profile_info->favourites->count(); @@ -55,23 +56,7 @@ $is_current_user = ($current_user !== null) && ($profile_info->id == $current_us updatePageViews(); -if (isset($_GET['del'])) { - if ($current_user !== null) { // Prevent unauthorized deletes - $paste_id = intval(trim($_GET['id'])); - $paste = Paste::find($paste_id); - - if (!$paste || $paste->user_id !== $current_user->id) { - $error = 'That paste does not exist, or you are not the owner of it.'; - } else { - $paste->delete(); - $success = 'Paste deleted successfully.'; - } - } else { - $error = 'You must be logged in to do that.'; - } -} - -// Theme +$csrf_token = setupCsrfToken(); $page_template = 'user_profile'; array_push($script_bundles, 'user_profile'); require_once('theme/' . $default_theme . '/common.php');