Compare commits
3 Commits
88ab0711bb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
58fc2f7acf
|
|||
|
98fdc290f4
|
|||
|
f29d3627e9
|
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# --- Build frontend ---
|
||||||
|
FROM node:24-slim AS frontend-builder
|
||||||
|
WORKDIR /frontend
|
||||||
|
COPY frontend/package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY frontend/ .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# --- Backend setup ---
|
||||||
|
FROM node:24-slim
|
||||||
|
|
||||||
|
# Install ping for backend
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y iputils-ping && \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy backend code
|
||||||
|
COPY backend/package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY backend/ .
|
||||||
|
|
||||||
|
# Copy built frontend into backend expected location
|
||||||
|
COPY --from=frontend-builder /frontend/dist /frontend/dist
|
||||||
|
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
CMD ["node", "index.js"]
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
FROM node:23-slim
|
FROM node:23-slim
|
||||||
|
|
||||||
|
RUN apt update && \
|
||||||
|
apt install -y iputils-ping \
|
||||||
|
&& apt clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const mysql = require('mysql2');
|
const mysql = require('mysql2');
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
|
const { exec } = require('child_process');
|
||||||
const app = express();
|
const app = express();
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
const db = require('./db');
|
const db = require('./db');
|
||||||
|
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
const serveStatic = express.static(path.join(__dirname, '../frontend/dist'));
|
||||||
|
|
||||||
app.post('/api/login', (req, res) => {
|
app.post('/api/login', (req, res) => {
|
||||||
const { username, password } = req.body;
|
const { username, password } = req.body;
|
||||||
@@ -35,4 +38,58 @@ app.post('/api/login', (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/api/ping', (req, res) => {
|
||||||
|
const { ip } = req.body;
|
||||||
|
|
||||||
|
// 🚨 INTENTIONALLY VULNERABLE TO COMMAND INJECTION
|
||||||
|
const command = `ping -c 4 ${ip}`;
|
||||||
|
exec(command, (err, stdout, stderr) => {
|
||||||
|
if (err) {
|
||||||
|
return res.status(500).json({
|
||||||
|
output: stderr,
|
||||||
|
command: command
|
||||||
|
});
|
||||||
|
}
|
||||||
|
res.json({
|
||||||
|
output: stdout,
|
||||||
|
command: command
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Endpoint to add a new post
|
||||||
|
app.post('/api/posts', (req, res) => {
|
||||||
|
const { post } = req.body;
|
||||||
|
|
||||||
|
if (!post || post.trim() === '') {
|
||||||
|
return res.status(400).json({ message: 'Post content cannot be empty' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = 'INSERT INTO posts (content) VALUES (?)';
|
||||||
|
db.query(query, [post], (err, results) => {
|
||||||
|
if (err) {
|
||||||
|
return res.status(500).json({ message: 'Error adding post', error: err });
|
||||||
|
}
|
||||||
|
res.json({ message: 'Post added successfully', postId: results.insertId });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Endpoint to get all posts
|
||||||
|
app.get('/api/posts', (req, res) => {
|
||||||
|
const query = 'SELECT * FROM posts';
|
||||||
|
db.query(query, (err, results) => {
|
||||||
|
if (err) {
|
||||||
|
return res.status(500).json({ message: 'Error fetching posts', error: err });
|
||||||
|
}
|
||||||
|
res.json({ posts: results });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
if (!req.path.startsWith('/api')) {
|
||||||
|
return serveStatic(req, res, next);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
app.listen(5000, () => console.log('Backend running on port 5000'));
|
app.listen(5000, () => console.log('Backend running on port 5000'));
|
||||||
|
|||||||
@@ -4,5 +4,10 @@ CREATE TABLE users (
|
|||||||
password VARCHAR(255)
|
password VARCHAR(255)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE posts (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
content TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
INSERT INTO users (username, password) VALUES ('admin', 'admin123');
|
INSERT INTO users (username, password) VALUES ('admin', 'admin123');
|
||||||
INSERT INTO users (username, password) VALUES ('user', 'password');
|
INSERT INTO users (username, password) VALUES ('user', 'password');
|
||||||
|
|||||||
@@ -15,19 +15,13 @@ services:
|
|||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
backend:
|
service:
|
||||||
build: ./backend
|
build: ./
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "5000:5000"
|
- "5000:5000"
|
||||||
|
cap_add:
|
||||||
|
- NET_RAW
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
frontend:
|
|
||||||
build: ./frontend
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
depends_on:
|
|
||||||
- backend
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom';
|
import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom';
|
||||||
import SQLInjection from './pages/SQLInjection';
|
import SQLInjection from './pages/SQLInjection';
|
||||||
import { Navbar, Nav, Container } from 'react-bootstrap';
|
import { Navbar, Nav, Container } from 'react-bootstrap';
|
||||||
|
import CommandInjection from './pages/CommandInjection';
|
||||||
|
import XSS from './pages/XSS';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -12,6 +14,8 @@ function App() {
|
|||||||
<Navbar.Collapse id="basic-navbar-nav">
|
<Navbar.Collapse id="basic-navbar-nav">
|
||||||
<Nav className="me-auto">
|
<Nav className="me-auto">
|
||||||
<Nav.Link as={Link} to="/sqli">SQL Injection</Nav.Link>
|
<Nav.Link as={Link} to="/sqli">SQL Injection</Nav.Link>
|
||||||
|
<Nav.Link as={Link} to="/cmdi">Command Injection</Nav.Link>
|
||||||
|
<Nav.Link as={Link} to="/xss">XSS</Nav.Link>
|
||||||
</Nav>
|
</Nav>
|
||||||
</Navbar.Collapse>
|
</Navbar.Collapse>
|
||||||
</Container>
|
</Container>
|
||||||
@@ -19,6 +23,8 @@ function App() {
|
|||||||
<div style={{ padding: 20 }}>
|
<div style={{ padding: 20 }}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/sqli" element={<SQLInjection />} />
|
<Route path="/sqli" element={<SQLInjection />} />
|
||||||
|
<Route path="/cmdi" element={<CommandInjection />} />
|
||||||
|
<Route path="/xss" element={<XSS />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
68
frontend/src/pages/CommandInjection.jsx
Normal file
68
frontend/src/pages/CommandInjection.jsx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Card, Container, Form, Button, Alert } from "react-bootstrap";
|
||||||
|
|
||||||
|
export default function CommandInjection() {
|
||||||
|
const [ipAddress, setIpAddress] = useState("");
|
||||||
|
const [output, setOutput] = useState("");
|
||||||
|
const [command, setCommand] = useState("");
|
||||||
|
const [showCommand, setShowCommand] = useState(false);
|
||||||
|
const [status, setStatus] = useState(null);
|
||||||
|
|
||||||
|
const handlePing = async () => {
|
||||||
|
const res = await fetch("http://localhost:5000/api/ping", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ ip: ipAddress }),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
setStatus(res.status);
|
||||||
|
setOutput(data.output);
|
||||||
|
setCommand(data.command);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="mt-5">
|
||||||
|
<Card className="shadow">
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title className="text-center">Ping Test</Card.Title>
|
||||||
|
<Form>
|
||||||
|
<Form.Group className="mb-3" controlId="formIpAddress">
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter IP Address"
|
||||||
|
onChange={(e) => setIpAddress(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group className="mb-3" controlId="formShowCommand">
|
||||||
|
<Form.Check
|
||||||
|
type="checkbox"
|
||||||
|
label="Show command"
|
||||||
|
checked={showCommand}
|
||||||
|
onChange={(e) => setShowCommand(e.target.checked)}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<div className="d-grid">
|
||||||
|
<Button variant="primary" onClick={handlePing}>
|
||||||
|
Ping
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
{output && (
|
||||||
|
<Alert
|
||||||
|
className={`mt-3 ${
|
||||||
|
status === 200 ? "alert-success" : "alert-warning"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<strong>Output:</strong> {output}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
{showCommand && command && (
|
||||||
|
<Alert className="mt-3 info">
|
||||||
|
<strong>Command:</strong> {command}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
74
frontend/src/pages/XSS.jsx
Normal file
74
frontend/src/pages/XSS.jsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Card, Container, Form, Button, Alert, ListGroup } from "react-bootstrap";
|
||||||
|
|
||||||
|
export default function XSS() {
|
||||||
|
const [posts, setPosts] = useState([]);
|
||||||
|
const [newPost, setNewPost] = useState("");
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
|
||||||
|
const handleAddPost = () => {
|
||||||
|
if (newPost.trim()) {
|
||||||
|
setPosts([...posts, newPost]);
|
||||||
|
setNewPost("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredPosts = posts.filter((post) =>
|
||||||
|
post.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="mt-5">
|
||||||
|
<Card className="shadow">
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title className="text-center">Posts</Card.Title>
|
||||||
|
|
||||||
|
{/* List of Posts */}
|
||||||
|
<h5>Posts</h5>
|
||||||
|
<ListGroup className="mb-3">
|
||||||
|
{filteredPosts.length > 0 ? (
|
||||||
|
filteredPosts.map((post, index) => (
|
||||||
|
<ListGroup.Item key={index}>
|
||||||
|
{/* Rendering posts directly (stored XSS vulnerability) */}
|
||||||
|
<span dangerouslySetInnerHTML={{ __html: post }} />
|
||||||
|
</ListGroup.Item>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<ListGroup.Item>No posts available</ListGroup.Item>
|
||||||
|
)}
|
||||||
|
</ListGroup>
|
||||||
|
|
||||||
|
{/* Add New Post */}
|
||||||
|
<Form>
|
||||||
|
<Form.Group className="mb-3" controlId="formNewPost">
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter new post"
|
||||||
|
value={newPost}
|
||||||
|
onChange={(e) => setNewPost(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<div className="d-grid">
|
||||||
|
<Button variant="primary" onClick={handleAddPost}>
|
||||||
|
Add Post
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
{/* Search Functionality */}
|
||||||
|
<h5 className="mt-4">Search</h5>
|
||||||
|
<Form>
|
||||||
|
<Form.Group className="mb-3" controlId="formSearchQuery">
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter search query"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
</Form>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user