Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 60 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,62 @@
# Decrypt
### [+] Created By HTR-TECH (@***tahmid.rayat***)
### [+] Disclaimer :
***Decrypter is a tool to decrypt Encrypted Bash Scripts into a Readable Format.This Tool is created for Educational Purpose only.I am not responsible for any misuse of this tool.***

<img src="https://raw.githubusercontent.com/htr-tech/release-download/master/images/decrypter.png" alt="" border="0" />

### [+] Installation
```apt update```

```apt install git python2 -y```

```git clone https://github.com/hax0rtahm1d/decrypt```

```cd decrypt```

```python2 dec.py```

### Or, Use Single Command

```
apt update && apt install git python2 -y && git clone https://github.com/hax0rtahm1d/decrypt && cd decrypt && python2 dec.py
# AVSTube

Website chia sẻ video dùng **PHP 8 + MySQL + Bootstrap 5**.

## 1) Cấu trúc thư mục

```text
/project (repo root)
├── admin/
│ ├── index.php
│ ├── login.php
│ ├── logout.php
│ ├── dashboard.php
│ ├── videos.php
│ ├── ads.php
│ ├── popup.php
│ ├── announcements.php
│ ├── settings.php
│ ├── auth.php
│ └── partials.php
├── assets/
│ ├── css/style.css
│ └── js/main.js
├── uploads/
│ ├── thumbnails/
│ └── videos/
├── sql/avstube.sql
├── config.php
├── index.php
├── video.php
├── search.php
├── comment.php
└── README.md
```

## [+] Find Me on :
[![Github](https://img.shields.io/badge/Github-HTR--TECH-green?style=for-the-badge&logo=github)](https://github.com/htr-tech)
[![Instagram](https://img.shields.io/badge/IG-%40tahmid.rayat-red?style=for-the-badge&logo=instagram)](https://www.instagram.com/tahmid.rayat)
[![Messenger](https://img.shields.io/badge/Chat-Messenger-blue?style=for-the-badge&logo=messenger)](https://m.me/tahmid.rayat.official)
## 2) Cài đặt

1. Tạo virtual host trỏ vào thư mục project.
2. Tạo database và import file SQL:
```bash
mysql -u root -p < sql/avstube.sql
```
3. Cập nhật DB trong `config.php` nếu khác mặc định.
4. Cấp quyền ghi cho `uploads/`.
5. Truy cập website tại `/index.php`.

## 3) Tài khoản admin mặc định

- Username: `admin`
- Password: `admin2006`
- URL: `/admin/login.php`

## 4) Tính năng chính

- Giao diện dark theo mẫu HTML yêu cầu (Bootstrap + Font Awesome).
- Trang chủ grid video, hover scale, responsive.
- Xem video hỗ trợ `embed` và `mp4`.
- Bình luận theo video.
- Tìm kiếm video theo tiêu đề.
- Quản trị đầy đủ: dashboard/chart, video CRUD, ads, popup ads, announcements, settings.
- Popup quảng cáo full màn hình, redirect khi click/X, ẩn vĩnh viễn bằng `localStorage`.
- Prepared statements + validate input cơ bản.
37 changes: 37 additions & 0 deletions admin/ads.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/partials.php';

if (isset($_GET['toggle'])) {
$id = (int) $_GET['toggle'];
$pdo->prepare('UPDATE ads SET status = IF(status=1,0,1) WHERE id=:id')->execute(['id' => $id]);
header('Location: ads.php');
exit;
}
if (isset($_GET['delete'])) {
$pdo->prepare('DELETE FROM ads WHERE id=:id')->execute(['id' => (int) $_GET['delete']]);
header('Location: ads.php');
exit;
}
Comment on lines +5 to +15

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

CSRF vulnerability: state-changing operations via GET requests.

Both toggle and delete actions use GET requests without CSRF protection. These can be exploited via image tags, links, or other cross-site vectors to modify ad state without authorization.

Convert these to POST requests with CSRF token validation, similar to the fix suggested for admin/announcements.php.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/ads.php` around lines 5 - 15, Change the state-changing GET handlers
for 'toggle' and 'delete' to accept POST and validate a CSRF token: stop reading
$_GET['toggle'] and $_GET['delete'], read $_POST['toggle'] and $_POST['delete']
instead, and before executing the prepared statements (the UPDATE in
prepare('UPDATE ads SET status = IF(status=1,0,1) WHERE id=:id') and the DELETE
in prepare('DELETE FROM ads WHERE id=:id')), call your CSRF validation helper
(e.g., validate_csrf_token($_POST['csrf_token']) or the same token check used in
admin/announcements.php), abort with a redirect/error if the token is invalid,
and update the UI forms that trigger these actions to use method="POST" and
include the csrf_token hidden field.

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$type = in_array($_POST['type'] ?? '', ['banner', 'google'], true) ? $_POST['type'] : 'banner';
$image = trim($_POST['image'] ?? '');
$link = trim($_POST['link'] ?? '');
$pdo->prepare('INSERT INTO ads (type,image,link,status) VALUES (:type,:image,:link,1)')->execute(compact('type', 'image', 'link'));
header('Location: ads.php');
exit;
}
Comment on lines +16 to +23

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing CSRF protection and URL validation on ad creation.

  1. The POST handler lacks CSRF token validation.
  2. The image field accepts any input without validation. For banner ads, this could allow storing malicious URLs (e.g., javascript: schemes) that would be rendered elsewhere.
🔒 Proposed fix
 if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+    if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== ($_SESSION['csrf_token'] ?? '')) {
+        http_response_code(403);
+        exit('Invalid CSRF token');
+    }
     $type = in_array($_POST['type'] ?? '', ['banner', 'google'], true) ? $_POST['type'] : 'banner';
     $image = trim($_POST['image'] ?? '');
     $link = trim($_POST['link'] ?? '');
+    
+    // Validate URLs for banner type
+    if ($type === 'banner') {
+        if (!filter_var($image, FILTER_VALIDATE_URL) || !preg_match('/^https?:\/\//', $image)) {
+            $image = '';
+        }
+        if ($link && (!filter_var($link, FILTER_VALIDATE_URL) || !preg_match('/^https?:\/\//', $link))) {
+            $link = '';
+        }
+    }
+    
     $pdo->prepare('INSERT INTO ads (type,image,link,status) VALUES (:type,:image,:link,1)')->execute(compact('type', 'image', 'link'));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$type = in_array($_POST['type'] ?? '', ['banner', 'google'], true) ? $_POST['type'] : 'banner';
$image = trim($_POST['image'] ?? '');
$link = trim($_POST['link'] ?? '');
$pdo->prepare('INSERT INTO ads (type,image,link,status) VALUES (:type,:image,:link,1)')->execute(compact('type', 'image', 'link'));
header('Location: ads.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== ($_SESSION['csrf_token'] ?? '')) {
http_response_code(403);
exit('Invalid CSRF token');
}
$type = in_array($_POST['type'] ?? '', ['banner', 'google'], true) ? $_POST['type'] : 'banner';
$image = trim($_POST['image'] ?? '');
$link = trim($_POST['link'] ?? '');
// Validate URLs for banner type
if ($type === 'banner') {
if (!filter_var($image, FILTER_VALIDATE_URL) || !preg_match('/^https?:\/\//', $image)) {
$image = '';
}
if ($link && (!filter_var($link, FILTER_VALIDATE_URL) || !preg_match('/^https?:\/\//', $link))) {
$link = '';
}
}
$pdo->prepare('INSERT INTO ads (type,image,link,status) VALUES (:type,:image,:link,1)')->execute(compact('type', 'image', 'link'));
header('Location: ads.php');
exit;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/ads.php` around lines 16 - 23, The POST handler currently allows
creating ads without CSRF validation and accepts arbitrary image URLs; add a
CSRF token check at the start of the POST branch (verify a submitted token
against the session token) and abort if missing/invalid, then validate/sanitize
the input values before calling $pdo->prepare(...)->execute(...): use the same
in_array(...) logic for $type, trim inputs, validate the $link and $image with
filter_var(..., FILTER_VALIDATE_URL) and enforce allowed schemes (only http and
https) for banner ads (reject or normalize javascript: and other schemes), and
ensure you escape or canonicalize values as needed before inserting with the
existing prepared statement to prevent storing malicious URLs.

$ads = $pdo->query("SELECT * FROM ads WHERE type IN ('banner','google') ORDER BY id DESC")->fetchAll();
adminHeader('Ads');
?>
<h2>Ads</h2>
<div class="card card-dark p-3 mb-4"><form method="post" class="row g-2">
<div class="col-md-3"><select name="type" class="form-select"><option value="banner">Banner</option><option value="google">Google Code</option></select></div>
<div class="col-md-4"><input name="image" class="form-control" placeholder="Image URL hoặc mã Google Ads"></div>
<div class="col-md-4"><input name="link" class="form-control" placeholder="Link redirect (banner)"></div>
<div class="col-md-1"><button class="btn btn-danger w-100">Add</button></div>
</form></div>
<table class="table table-dark table-striped"><thead><tr><th>ID</th><th>Type</th><th>Status</th><th>Action</th></tr></thead><tbody>
<?php foreach ($ads as $ad): ?><tr><td><?= (int) $ad['id'] ?></td><td><?= e($ad['type']) ?></td><td><?= $ad['status'] ? 'ON' : 'OFF' ?></td><td><a href="ads.php?toggle=<?= (int) $ad['id'] ?>" class="btn btn-sm btn-warning">Toggle</a> <a href="ads.php?delete=<?= (int) $ad['id'] ?>" class="btn btn-sm btn-danger">Delete</a></td></tr><?php endforeach; ?>
</tbody></table>
<?php adminFooter(); ?>
25 changes: 25 additions & 0 deletions admin/announcements.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/partials.php';

if (isset($_GET['delete'])) {
$pdo->prepare('DELETE FROM announcements WHERE id=:id')->execute(['id' => (int) $_GET['delete']]);
header('Location: announcements.php');
exit;
}
Comment on lines +5 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

CSRF vulnerability: state-changing DELETE via GET request.

The delete action is triggered via a GET request, which is vulnerable to CSRF attacks (e.g., an attacker can embed <img src="announcements.php?delete=1"> to delete announcements). State-changing operations should use POST requests with CSRF token validation.

🔒 Proposed fix: use POST with CSRF token
-if (isset($_GET['delete'])) {
-    $pdo->prepare('DELETE FROM announcements WHERE id=:id')->execute(['id' => (int) $_GET['delete']]);
-    header('Location: announcements.php');
-    exit;
-}
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete'])) {
+    if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== ($_SESSION['csrf_token'] ?? '')) {
+        http_response_code(403);
+        exit('Invalid CSRF token');
+    }
+    $pdo->prepare('DELETE FROM announcements WHERE id=:id')->execute(['id' => (int) $_POST['delete']]);
+    header('Location: announcements.php');
+    exit;
+}

Then update the delete button in the table to use a form:

<form method="post" style="display:inline">
    <input type="hidden" name="csrf_token" value="<?= e($_SESSION['csrf_token'] ?? '') ?>">
    <input type="hidden" name="delete" value="<?= (int)$r['id'] ?>">
    <button class="btn btn-sm btn-danger">Delete</button>
</form>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (isset($_GET['delete'])) {
$pdo->prepare('DELETE FROM announcements WHERE id=:id')->execute(['id' => (int) $_GET['delete']]);
header('Location: announcements.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete'])) {
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== ($_SESSION['csrf_token'] ?? '')) {
http_response_code(403);
exit('Invalid CSRF token');
}
$pdo->prepare('DELETE FROM announcements WHERE id=:id')->execute(['id' => (int) $_POST['delete']]);
header('Location: announcements.php');
exit;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/announcements.php` around lines 5 - 9, The delete flow currently uses
$_GET['delete'] and executes a state-changing DELETE via GET; change this to
accept only POST by checking $_SERVER['REQUEST_METHOD'] === 'POST' and
isset($_POST['delete']), validate a CSRF token from $_POST['csrf_token'] against
$_SESSION['csrf_token'], then perform the $pdo->prepare('DELETE FROM
announcements WHERE id=:id')->execute(['id' => (int) $_POST['delete']]) and
redirect as before; also update the UI so the delete button submits a POST form
including the hidden csrf_token and delete id.

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$title = trim($_POST['title'] ?? '');
$content = trim($_POST['content'] ?? '');
if ($title !== '' && $content !== '') {
$pdo->prepare('INSERT INTO announcements (title, content) VALUES (:title, :content)')->execute(compact('title', 'content'));
}
header('Location: announcements.php');
exit;
}
Comment on lines +10 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing CSRF protection on POST form submission.

The announcement creation form lacks CSRF token validation, making it vulnerable to cross-site request forgery attacks.

🔒 Proposed fix: add CSRF token validation
 if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+    if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== ($_SESSION['csrf_token'] ?? '')) {
+        http_response_code(403);
+        exit('Invalid CSRF token');
+    }
     $title = trim($_POST['title'] ?? '');
     $content = trim($_POST['content'] ?? '');

And add the token field to the form in Line 23:

<input type="hidden" name="csrf_token" value="<?= e($_SESSION['csrf_token'] ?? '') ?>">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/announcements.php` around lines 10 - 18, Add CSRF token
generation/validation: ensure a CSRF token is stored in $_SESSION['csrf_token']
and add a hidden form field named "csrf_token" (use the existing e() helper when
rendering) in the announcement creation form; then, in the POST handling branch
(where $_SERVER['REQUEST_METHOD'] === 'POST' and before inserting via
$pdo->prepare(...)->execute(...)), validate that $_POST['csrf_token'] exists and
exactly matches $_SESSION['csrf_token'], and if it fails, abort (e.g., redirect
or show error) instead of executing the DB insert to prevent CSRF.

$rows = $pdo->query('SELECT * FROM announcements ORDER BY id DESC')->fetchAll();
adminHeader('Announcements');
?>
<h2>Announcements</h2>
<div class="card card-dark p-3 mb-3"><form method="post" class="row g-2"><div class="col-md-4"><input class="form-control" name="title" required placeholder="Title"></div><div class="col-md-6"><input class="form-control" name="content" required placeholder="Content"></div><div class="col-md-2"><button class="btn btn-danger w-100">Add</button></div></form></div>
<table class="table table-dark"><thead><tr><th>ID</th><th>Title</th><th>Content</th><th></th></tr></thead><tbody><?php foreach($rows as $r): ?><tr><td><?= (int)$r['id'] ?></td><td><?= e($r['title']) ?></td><td><?= e($r['content']) ?></td><td><a class="btn btn-sm btn-danger" href="announcements.php?delete=<?= (int)$r['id'] ?>">Delete</a></td></tr><?php endforeach; ?></tbody></table>
<?php adminFooter(); ?>
3 changes: 3 additions & 0 deletions admin/auth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php
require_once __DIR__ . '/../config.php';
adminOnly();
39 changes: 39 additions & 0 deletions admin/dashboard.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/partials.php';

$totalVideos = (int) $pdo->query('SELECT COUNT(*) FROM videos')->fetchColumn();
$totalViews = (int) $pdo->query('SELECT COALESCE(SUM(views),0) FROM videos')->fetchColumn();
$todayViews = (int) $pdo->query('SELECT COALESCE(SUM(daily_views),0) FROM video_stats WHERE view_date = CURDATE()')->fetchColumn();
$topVideo = $pdo->query('SELECT title, views FROM videos ORDER BY views DESC LIMIT 1')->fetch();

$chartRows = $pdo->query('SELECT view_date, COALESCE(SUM(daily_views),0) AS views FROM video_stats GROUP BY view_date ORDER BY view_date DESC LIMIT 7')->fetchAll();
$labels = [];
$values = [];
foreach (array_reverse($chartRows) as $row) {
$labels[] = $row['view_date'];
$values[] = (int) $row['views'];
}

adminHeader('Dashboard');
?>
<h2>Dashboard</h2>
<div class="row g-3 mb-4">
<div class="col-md-3"><div class="card card-dark p-3"><small>Total Video</small><h3><?= $totalVideos ?></h3></div></div>
<div class="col-md-3"><div class="card card-dark p-3"><small>Total Views</small><h3><?= number_format($totalViews) ?></h3></div></div>
<div class="col-md-3"><div class="card card-dark p-3"><small>Views Today</small><h3><?= number_format($todayViews) ?></h3></div></div>
<div class="col-md-3"><div class="card card-dark p-3"><small>Top Video</small><h6 class="mb-0"><?= e($topVideo['title'] ?? 'N/A') ?></h6></div></div>
</div>
<div class="card card-dark p-3">
<h5>Thống kê theo ngày</h5>
<canvas id="viewsChart" height="100"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
new Chart(document.getElementById('viewsChart'), {
type: 'line',
data: { labels: <?= json_encode($labels) ?>, datasets: [{label: 'Views', data: <?= json_encode($values) ?>, borderColor: '#c00', tension: 0.4}]},
options: { plugins: {legend: {labels:{color:'#ddd'}}}, scales: {x:{ticks:{color:'#aaa'}}, y:{ticks:{color:'#aaa'}}}}
});
</script>
<?php adminFooter(); ?>
4 changes: 4 additions & 0 deletions admin/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php
require_once __DIR__ . '/../config.php';
header('Location: ' . (isAdmin() ? 'dashboard.php' : 'login.php'));
exit;
37 changes: 37 additions & 0 deletions admin/login.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
require_once __DIR__ . '/../config.php';

if (isAdmin()) {
header('Location: dashboard.php');
exit;
}

$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username'] ?? '');
$password = trim($_POST['password'] ?? '');

$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username LIMIT 1');
$stmt->execute(['username' => $username]);
$user = $stmt->fetch();

if ($user && password_verify($password, $user['password']) && $user['role'] === 'admin') {
$_SESSION['admin_id'] = $user['id'];
$_SESSION['admin_username'] = $user['username'];
header('Location: dashboard.php');
exit;
}
$error = 'Sai thông tin đăng nhập';
}
?>
<!DOCTYPE html>
<html lang="vi"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Admin Login</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>body{background:#000;color:#ddd;display:flex;align-items:center;justify-content:center;min-height:100vh}.box{width:100%;max-width:420px;background:#111;border:1px solid #222;border-radius:12px;padding:24px}</style>
</head><body>
<div class="box"><h3 class="text-center text-danger mb-3">AVSTube Admin</h3>
<?php if ($error): ?><div class="alert alert-danger"><?= e($error) ?></div><?php endif; ?>
<form method="post"><input class="form-control bg-dark text-light border-secondary mb-2" name="username" required placeholder="Username">
<input type="password" class="form-control bg-dark text-light border-secondary mb-3" name="password" required placeholder="Password">
<button class="btn btn-danger w-100">Đăng nhập</button></form></div>
</body></html>
5 changes: 5 additions & 0 deletions admin/logout.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php
require_once __DIR__ . '/../config.php';
session_destroy();
header('Location: login.php');
exit;
25 changes: 25 additions & 0 deletions admin/partials.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
function adminHeader(string $title = 'Admin'): void
{
echo '<!DOCTYPE html><html lang="vi"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">';
echo '<title>' . e($title) . ' - AVSTube Admin</title>';
echo '<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">';
echo '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">';
echo '<style>body{background:#0b0b0b;color:#ddd} .sidebar{background:#111;min-height:100vh;padding:20px} .sidebar a{display:block;color:#bbb;padding:10px;border-radius:8px;margin-bottom:6px} .sidebar a:hover,.sidebar a.active{background:#c00;color:#fff} .card-dark{background:#111;border:1px solid #222} .table{color:#ddd} .form-control,.form-select,textarea{background:#1a1a1a!important;color:#ddd!important;border-color:#333!important}</style>';
echo '</head><body><div class="container-fluid"><div class="row">';
echo '<aside class="col-md-3 col-lg-2 sidebar">';
echo '<h3 class="text-danger">AVSTube</h3>';
echo '<a href="dashboard.php"><i class="fa fa-chart-line"></i> Dashboard</a>';
echo '<a href="videos.php"><i class="fa fa-video"></i> Videos</a>';
echo '<a href="ads.php"><i class="fa fa-bullhorn"></i> Ads</a>';
echo '<a href="popup.php"><i class="fa fa-up-right-and-down-left-from-center"></i> Popup Ads</a>';
echo '<a href="announcements.php"><i class="fa fa-bell"></i> Announcements</a>';
echo '<a href="settings.php"><i class="fa fa-gear"></i> Settings</a>';
echo '<a href="logout.php"><i class="fa fa-right-from-bracket"></i> Logout</a>';
echo '</aside><main class="col-md-9 col-lg-10 p-4">';
}

function adminFooter(): void
{
echo '</main></div></div><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script></body></html>';
}
32 changes: 32 additions & 0 deletions admin/popup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/partials.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$image = trim($_POST['image'] ?? '');
$link1 = trim($_POST['link1'] ?? '');
$link2 = trim($_POST['link2'] ?? '');
$links = implode(',', array_filter([$link1, $link2]));

$pdo->exec("DELETE FROM ads WHERE type='popup'");
$stmt = $pdo->prepare("INSERT INTO ads (type,image,link,status) VALUES ('popup',:image,:link,1)");
$stmt->execute(['image' => $image, 'link' => $links]);
header('Location: popup.php?saved=1');
exit;
}

$popup = $pdo->query("SELECT * FROM ads WHERE type='popup' ORDER BY id DESC LIMIT 1")->fetch();
$links = array_values(array_filter(array_map('trim', explode(',', (string) ($popup['link'] ?? '')))));
adminHeader('Popup Ads');
?>
<h2>Popup Ads</h2>
<?php if (isset($_GET['saved'])): ?><div class="alert alert-success">Saved</div><?php endif; ?>
<div class="card card-dark p-3">
<form method="post" class="row g-3">
<div class="col-md-12"><label>Popup image URL</label><input name="image" class="form-control" required value="<?= e($popup['image'] ?? '') ?>"></div>
<div class="col-md-6"><label>Link quảng cáo 1</label><input name="link1" class="form-control" required value="<?= e($links[0] ?? '') ?>"></div>
<div class="col-md-6"><label>Link quảng cáo 2 (optional)</label><input name="link2" class="form-control" value="<?= e($links[1] ?? '') ?>"></div>
<div class="col-12"><button class="btn btn-danger">Lưu popup</button></div>
</form>
</div>
<?php adminFooter(); ?>
26 changes: 26 additions & 0 deletions admin/settings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/partials.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$logo = trim($_POST['site_logo'] ?? '');
$banner = trim($_POST['site_banner'] ?? '');

foreach (['site_logo' => $logo, 'site_banner' => $banner] as $key => $value) {
$stmt = $pdo->prepare('INSERT INTO settings (setting_key, setting_value) VALUES (:k, :v) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)');
$stmt->execute(['k' => $key, 'v' => $value]);
}
header('Location: settings.php?saved=1');
exit;
}
Comment on lines +5 to +15

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing CSRF protection on settings form.

Admin forms should include CSRF token validation to prevent cross-site request forgery attacks, especially for state-changing operations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/settings.php` around lines 5 - 15, Add CSRF token generation and
validation around the POST handling in admin/settings.php: when rendering the
settings form generate/store a token in session (e.g. $_SESSION['csrf_token']),
include it as a hidden field in the form, and in the POST branch (the block
starting with if ($_SERVER['REQUEST_METHOD'] === 'POST') that reads
$logo/$banner and updates the DB) validate the submitted token exists and
matches the session token using a timing-safe comparison (e.g. hash_equals); if
the token is missing/invalid, stop processing (return a 400/403 or redirect)
before executing the DB updates and header redirect. Ensure the session is
started when generating/validating the token and optionally regenerate the token
after successful submission.

adminHeader('Settings');
?>
<h2>Settings</h2>
<?php if (isset($_GET['saved'])): ?><div class="alert alert-success">Đã lưu cài đặt</div><?php endif; ?>
<div class="card card-dark p-3">
<form method="post" class="row g-3">
<div class="col-md-12"><label>Logo URL</label><input class="form-control" name="site_logo" value="<?= e(setting($pdo, 'site_logo')) ?>"></div>
<div class="col-md-12"><label>Banner URL</label><input class="form-control" name="site_banner" value="<?= e(setting($pdo, 'site_banner')) ?>"></div>
<div class="col-md-12"><button class="btn btn-danger">Lưu</button></div>
</form></div>
<?php adminFooter(); ?>
Loading