Compare commits

...

9 commits

Author SHA1 Message Date
Vomitblood fb1e39ed1c combined all docker compose to one big chungus 2025-02-13 02:45:16 +08:00
Vomitblood e58620fc42 asdf 2025-02-13 02:45:00 +08:00
Vomitblood b677352f4f added docker image for server 2025-02-13 02:44:55 +08:00
Vomitblood 9caedb4480 added cli flags functionality 2025-02-13 02:44:44 +08:00
Vomitblood 59832d8e11 asdf 2025-02-13 02:44:26 +08:00
Vomitblood 601a7acb16 added docker image for server-ml 2025-02-13 02:44:22 +08:00
Vomitblood 9bee70c106 changed response format 2025-02-13 02:44:00 +08:00
Vomitblood b2250966fb added setup page 2025-02-13 02:43:35 +08:00
Vomitblood 43510fb367 asdf 2025-02-13 02:43:16 +08:00
30 changed files with 438 additions and 210 deletions

View file

@ -1,5 +1,15 @@
# CSPJ Application
## Setup
### Requirements
User to be added into `docker` group.
```bash
sudo usermod -aG docker $USER
```
## Services
- 3331: Apache + ModSecurity

View file

@ -2,7 +2,6 @@ import { Box, Stack } from "@mui/material";
import { WindowButtons } from "./WindowButtons";
import { NavigationButtons } from "./NavigationButtons";
import { RouteDisplay } from "./RouteDisplay";
import { Testing } from "../Testing/Testing";
import { ServerStatus } from "./ServerStatus";
export const HeaderBar = () => {
@ -73,7 +72,6 @@ export const HeaderBar = () => {
}}
>
<ServerStatus />
<Testing />
<WindowButtons />
</Stack>
</Box>

View file

@ -1,7 +1,7 @@
import { Box, Button, Chip, CircularProgress, Popover, Stack } from "@mui/material";
import { fetch } from "@tauri-apps/plugin-http";
import { useAtom } from "jotai";
import { MouseEvent, useEffect, useState } from "react";
import { MouseEvent, useCallback, useEffect, useState } from "react";
import { serverConnectionAtom, serverUrlAtom } from "../../lib/jotai";
import { defaultSettings } from "../../lib/settings";
import { ServerUrlInput } from "./ServerUrlInput";
@ -24,7 +24,7 @@ export const ServerStatus = () => {
};
// function to check server health
const checkServerConnection = async () => {
const checkServerConnection = useCallback(async () => {
setServerConnection("connecting");
// remove trailing slash
@ -42,7 +42,7 @@ export const ServerStatus = () => {
await sleep(500);
setServerConnection("disconnected");
}
};
}, [serverUrl, setServerConnection, setServerUrl]);
const chipProps = {
color: serverConnection === "connected" ? "success" : serverConnection === "disconnected" ? "error" : "warning",
@ -64,7 +64,7 @@ export const ServerStatus = () => {
clearInterval(intervalId);
};
}
}, [serverConnection, serverUrl]);
}, [checkServerConnection, serverConnection, serverUrl]);
return (
<Box

View file

@ -0,0 +1,28 @@
import { Grid2 } from "@mui/material";
import { FC } from "react";
type SetupLogItemProps = {
children?: string;
};
export const SetupLogItem: FC<SetupLogItemProps> = ({ children }) => {
return (
<Grid2
color="lightgreen"
container
fontFamily="JetBrainsMono"
fontSize="0.9rem"
spacing={1}
>
<Grid2 size="auto">&gt;</Grid2>
<Grid2
size="grow"
sx={{
wordBreak: "break-word",
}}
>
{children}
</Grid2>
</Grid2>
);
};

View file

@ -3,13 +3,14 @@ import { Box, Divider, Tab, useTheme } from "@mui/material";
import { useState } from "react";
import { SqlInjectionLogin } from "./SqlInjectionLogin";
import { SqlInjectionRegister } from "./SqlInjectionRegister";
import { SqlInjectionSetup } from "./SqlInjectionSetup";
export const SqlInjection = () => {
// contexts
const theme = useTheme();
// states
const [subTabValue, setSubTabValue] = useState("register");
const [subTabValue, setSubTabValue] = useState("setup");
// logic for switching tabs
const subTabChangeEvent = (newTabValue: string) => {
@ -37,7 +38,7 @@ export const SqlInjection = () => {
>
<TabContext value={subTabValue}>
<TabList
onChange={(e, value) => {
onChange={(_, value) => {
subTabChangeEvent(value);
}}
scrollButtons={true}
@ -46,6 +47,10 @@ export const SqlInjection = () => {
}}
variant="standard"
>
<Tab
label="Setup"
value="setup"
/>
<Tab
label="Register"
value="register"
@ -66,6 +71,12 @@ export const SqlInjection = () => {
width: "100%",
}}
>
<TabPanel
sx={{ p: 2 }}
value="setup"
>
<SqlInjectionSetup />
</TabPanel>
<TabPanel
sx={{ p: 2 }}
value="register"

View file

@ -0,0 +1,120 @@
import { LoadingButton } from "@mui/lab";
import { Box, FormControlLabel, Grid, Grid2, Switch, TextField, Typography } from "@mui/material";
import { fetch } from "@tauri-apps/plugin-http";
import { useAtom } from "jotai";
import { useState } from "react";
import { useNotification } from "../../../contexts/NotificationContext";
import { serverUrlAtom } from "../../../lib/jotai";
import { HeaderLogo } from "../../Generic/HeaderLogo";
import { SetupLogItem } from "./SetupLogItem";
export const SqlInjectionSetup = () => {
// contexts
const { openNotification } = useNotification();
// atoms
const [serverUrl, setServerUrl] = useAtom(serverUrlAtom);
// states
const [logs, setLogs] = useState<string[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const setupClickEvent = async () => {
setLoading(true);
try {
const response = await fetch(serverUrl + "/nuke-db", {
method: "POST",
});
const responseText = await response.text();
if (!response.ok) {
openNotification("Nuke failed: " + responseText);
} else {
openNotification("Nuke successful");
}
setLogs((prevLogs) => [...prevLogs.slice(-5), responseText]);
const response2 = await fetch(serverUrl + "/setup-demo-db", {
method: "POST",
});
const responseText2 = await response2.text();
if (!response.ok) {
openNotification("Setup demo DB failed: " + responseText2);
} else {
openNotification("Demo database setup successful");
}
setLogs((prevLogs) => [...prevLogs.slice(-5), responseText2]);
} catch (e) {
// log the error and handle failure
console.log("Request failed", e);
} finally {
// stop loading indicator regardless of success/failure
setLoading(false);
}
};
return (
<>
<Box
alignItems="center"
display="flex"
flexDirection="row"
sx={{
mb: 2,
}}
>
<HeaderLogo />
<Typography
sx={{
color: "grey",
}}
variant="h6"
>
&nbsp;Setup
</Typography>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
maxHeight: "100%",
}}
>
<Typography variant="body2">
Click on the &apos;Setup/reset DB&apos; button below to initialize or reset your database.
</Typography>
<Typography variant="body2">
If you get an error, make sure you have set the correct server URL (check the chip at the top right).
</Typography>
<Typography variant="body2">
Demo email:{" "}
<b>
<code style={{ fontFamily: "JetBrainsMono" }}>asdf@gmail.com</code>
</b>
</Typography>
<Typography variant="body2">
Demo password:{" "}
<b>
<code style={{ fontFamily: "JetBrainsMono" }}>asdf</code>
</b>
</Typography>
<LoadingButton
loading={loading}
onClick={setupClickEvent}
size="small"
sx={{
my: 2,
}}
variant="contained"
>
Setup/reset DB
</LoadingButton>
{logs.map((log, index) => (
<SetupLogItem key={index}>{log}</SetupLogItem>
))}
</Box>
</>
);
};

View file

@ -1,10 +0,0 @@
import { Box } from "@mui/material";
export const Xss = () => {
return (
<Box>
<h1>XSS</h1>
<p>Cross-site scripting (XSS) is a type of security vulnerability</p>
</Box>
);
};

View file

@ -1,74 +0,0 @@
import { BugReportOutlined } from "@mui/icons-material";
import { Box, Button, IconButton, useTheme } from "@mui/material";
import { fetch } from "@tauri-apps/plugin-http";
import { useAtom } from "jotai";
import { useState } from "react";
import { serverUrlAtom } from "../../lib/jotai";
import { defaultSettings } from "../../lib/settings";
import { FloatingDialog } from "../Generic/FloatingDialog";
export const Testing = () => {
// contexts
const theme = useTheme();
// atoms
const [serverUrl, setServerUrl] = useAtom(serverUrlAtom);
// states
const [openState, setOpenState] = useState(false);
const [maximisedState, setMaximisedState] = useState(false);
// functions
const close = () => setOpenState(false);
const testing = () => {
fetch(serverUrl + "/nuke-db").then((response) => {
console.log(response);
});
fetch(serverUrl + "/setup-demo-db").then((response) => {
console.log(response);
});
};
return (
<FloatingDialog
body={
<Box
sx={{
border: "1px solid " + theme.palette.grey[700],
borderRadius: defaultSettings.style.radius + "px",
display: "flex",
flexDirection: "column",
flexGrow: 1,
my: 2,
overflow: "hidden",
p: 0,
}}
>
<Box>
<Button
onClick={() => {
testing();
}}
>
test
</Button>
</Box>
</Box>
}
close={close}
maximisedState={maximisedState}
openButton={
<IconButton
onClick={() => setOpenState(true)}
size="small"
>
<BugReportOutlined />
</IconButton>
}
openState={openState}
setMaximisedState={setMaximisedState}
title="Testing"
/>
);
};

View file

@ -2,6 +2,7 @@ services:
postgres:
image: postgres:latest
container_name: postgres_db
restart: unless-stopped
environment:
POSTGRES_USER: asdfuser
POSTGRES_PASSWORD: asdfpassword
@ -14,7 +15,7 @@ services:
modsecurity:
image: owasp/modsecurity-crs:apache-alpine
container_name: modsecurity
restart: always
restart: unless-stopped
environment:
BACKEND: "http://localhost:80"
SERVER_NAME: "localhost"
@ -36,9 +37,27 @@ services:
dvwa:
image: vulnerables/web-dvwa
container_name: dvwa
restart: always
restart: unless-stopped
ports:
- "80:80"
cspj-server:
image: cspj-server
container_name: cspj-server
restart: unless-stopped
network_mode: host
depends_on:
- postgres
volumes:
- "/home/vomitblood/build/cspj-application/docker/chungus/logs/:/tmp"
command: ["server", "-l", "/tmp"]
cspj-ml-server:
image: cspj-ml-server
container_name: cspj-ml-server
restart: unless-stopped
ports:
- "5000:5000"
volumes:
postgres_data:

1
docker/chungus/remove.sh Normal file
View file

@ -0,0 +1 @@
docker container rm modsecurity suricata dvwa postgres_db

View file

@ -1,7 +1,11 @@
mkdir logs
touch ./logs/host-fs-auditlog.log
touch ./logs/host-fs-errorlog.log
touch ./logs/host-fs-accesslog.log
chmod 777 ./logs/host-fs-auditlog.log
chmod 777 ./logs/host-fs-errorlog.log
chmod 777 ./logs/host-fs-accesslog.log
chmod 777 ./logs/host-fs-accesslog.log
docker compose up

View file

@ -14,5 +14,5 @@ services:
volumes:
- ./suricata/etc:/etc/suricata
- ./suricata/logs:/var/log/suricata
- ./suricata/lib:/var/lib/suricata
# - ./suricata/lib:/var/lib/suricata
command: ["-c", "/etc/suricata/suricata.yaml", "--af-packet"]

11
server-ml/Dockerfile Normal file
View file

@ -0,0 +1,11 @@
FROM python:3.11.6-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py .
CMD ["python", "main.py"]

View file

@ -0,0 +1,46 @@
# Test Cases
## 1. Basic test cases
[Good] `SELECT _ FROM users WHERE id = 1`
[Bad] `SELECT _ FROM users WHERE id = 1 OR 1=1`
## 2. Authentication bypass cases
[Bad] `SELECT _ FROM users WHERE username = 'admin' --`
[Bad] `SELECT _ FROM users WHERE username = 'admin' #`
[Bad] `SELECT \* FROM users WHERE username = 'admin' OR '1'='1'`
## 3. Union based injection cases
[Bad] `SELECT id, username FROM users WHERE id = 1 UNION SELECT null, 'hacker'`
[Bad] `SELECT id, username FROM users WHERE id = 1 UNION SELECT 1, 'hacked' FROM dual`
[Bad] `SELECT database() UNION SELECT 1`
## 4. Error based injection cases
[Bad] `SELECT _ FROM users WHERE id = 1 AND (SELECT 1 FROM users WHERE id=2)=1`
[Bad] `SELECT _ FROM users WHERE id = (SELECT COUNT(\*) FROM users)`
## 5. Blind SQL injection cases
[Bad] `SELECT _ FROM users WHERE id = 1; WAITFOR DELAY '00:00:10' --`
[Bad] `SELECT _ FROM users WHERE username = 'admin' AND 1=1`
## 6. Hex and Base64 encoded injection cases
[Bad] `SELECT _ FROM users WHERE username = 0x61646D696E`
[Bad] `SELECT _ FROM users WHERE username = 'YWRtaW4='`
## 7. False positives cases
[Good] `SELECT _ FROM users WHERE id = 5`
[Good] `SELECT users.name, orders.amount FROM users JOIN orders ON users.id = orders.user_id`
[Good] `SELECT _ FROM users WHERE username = ? AND password = ?`
## 8. Edge cases
[Good] `""`
[Bad] `'; --`
[Good] `12345`
[Good] `asdkjhasdkjh`

View file

@ -0,0 +1,7 @@
services:
cspj-ml-server:
image: cspj-ml-server
container_name: cspj-ml-server
restart: unless-stopped
ports:
- "5000:5000"

View file

@ -2,19 +2,24 @@ from flask import Flask, request, jsonify
import torch
from transformers import MobileBertTokenizer, MobileBertForSequenceClassification
# Initialize Flask app
print("Starting server...")
# initialize Flask app
app = Flask(__name__)
# Set device (GPU if available, otherwise CPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# set device, use gpu if available
device = torch.device("cpu")
# device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# Load tokenizer and model
# load tokenizer and model
print("Loading model...")
tokenizer = MobileBertTokenizer.from_pretrained("google/mobilebert-uncased")
model = MobileBertForSequenceClassification.from_pretrained("cssupport/mobilebert-sql-injection-detect")
model.to(device)
model.eval()
print("Model loaded")
# Function to predict SQL injection
# function for model to predict sql injection
def predict(text):
inputs = tokenizer(text, padding=False, truncation=True, return_tensors="pt", max_length=512)
input_ids = inputs["input_ids"].to(device)
@ -30,7 +35,7 @@ def predict(text):
return predicted_class, confidence
# Define API endpoint
# the api endpoint
@app.route("/predict", methods=["POST"])
def classify_query():
data = request.json
@ -40,16 +45,16 @@ def classify_query():
query = data["query"]
predicted_class, confidence = predict(query)
# Thresholding (if confidence > 0.7, mark as SQL Injection)
# if >0.7, then mark as bad
is_vulnerable = predicted_class == 1 and confidence > 0.7
result = {
"query": query,
"classification": "SQL Injection Detected" if is_vulnerable else "No SQL Injection Detected",
"result": "fail" if is_vulnerable else "pass",
"confidence": round(confidence, 2)
}
return jsonify(result)
# Run Flask server
# run the flask server
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
app.run(host="0.0.0.0", port=5000, debug=False)

View file

@ -3,7 +3,7 @@ name = "server-ml"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
requires-python = ">=3.11"
dependencies = [
"ctransformers>=0.2.27",
"flask>=3.1.0",

View file

@ -1,3 +1,48 @@
flask
scikit-learn
joblib
accelerate==1.3.0
blinker==1.9.0
certifi==2025.1.31
charset-normalizer==3.4.1
click==8.1.8
ctransformers==0.2.27
filelock==3.17.0
flask==3.1.0
fsspec==2025.2.0
huggingface-hub==0.28.1
idna==3.10
itsdangerous==2.2.0
jinja2==3.1.5
markupsafe==3.0.2
mpmath==1.3.0
networkx==3.4.2
numpy==2.2.2
nvidia-cublas-cu12==12.4.5.8
nvidia-cuda-cupti-cu12==12.4.127
nvidia-cuda-nvrtc-cu12==12.4.127
nvidia-cuda-runtime-cu12==12.4.127
nvidia-cudnn-cu12==9.1.0.70
nvidia-cufft-cu12==11.2.1.3
nvidia-curand-cu12==10.3.5.147
nvidia-cusolver-cu12==11.6.1.9
nvidia-cusparse-cu12==12.3.1.170
nvidia-cusparselt-cu12==0.6.2
nvidia-nccl-cu12==2.21.5
nvidia-nvjitlink-cu12==12.4.127
nvidia-nvtx-cu12==12.4.127
packaging==24.2
peft==0.14.0
psutil==6.1.1
py-cpuinfo==9.0.0
pyyaml==6.0.2
regex==2024.11.6
requests==2.32.3
safetensors==0.5.2
setuptools==75.8.0
sympy==1.13.1
tokenizers==0.21.0
torch==2.6.0
tqdm==4.67.1
transformers==4.48.2
triton==3.2.0
typing-extensions==4.12.2
urllib3==2.3.0
werkzeug==3.1.3

View file

@ -1,46 +0,0 @@
# Test Cases
## 1. Basic test cases
[Good] `SELECT _ FROM users WHERE id = 1`
[Bad] `SELECT _ FROM users WHERE id = 1 OR 1=1`
## 2. Authentication bypass cases
[Bad] `SELECT _ FROM users WHERE username = 'admin' --`
[Bad] `SELECT _ FROM users WHERE username = 'admin' #`
[Bad] `SELECT \* FROM users WHERE username = 'admin' OR '1'='1'`
## 3. Union based injection cases
[Bad] `SELECT id, username FROM users WHERE id = 1 UNION SELECT null, 'hacker'`
[Bad] `SELECT id, username FROM users WHERE id = 1 UNION SELECT 1, 'hacked' FROM dual`
[Bad] `SELECT database() UNION SELECT 1`
## 4. Error based injection cases
[Bad] `SELECT _ FROM users WHERE id = 1 AND (SELECT 1 FROM users WHERE id=2)=1`
[Bad] `SELECT _ FROM users WHERE id = (SELECT COUNT(\*) FROM users)`
## 5. Blind SQL injection cases
[Bad] `SELECT _ FROM users WHERE id = 1; WAITFOR DELAY '00:00:10' --`
[Bad] `SELECT _ FROM users WHERE username = 'admin' AND 1=1`
## 6. Hex and Base64 encoded injection cases
[Bad] `SELECT _ FROM users WHERE username = 0x61646D696E`
[Bad] `SELECT _ FROM users WHERE username = 'YWRtaW4='`
## 7. False positives cases
[Good] `SELECT _ FROM users WHERE id = 5`
[Good] `SELECT users.name, orders.amount FROM users JOIN orders ON users.id = orders.user_id`
[Good] `SELECT _ FROM users WHERE username = ? AND password = ?`
## 8. Edge cases
[Good] `""`
[Bad] `'; --`
[Good] `12345`
[Good] `asdkjhasdkjh`

View file

@ -1,35 +0,0 @@
import joblib
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
# random data
data = [
("' OR '1'='1", 1),
("SELECT * FROM users WHERE id=1", 1),
("DROP TABLE users;", 1),
("username=admin'--", 1),
("hello world", 0),
("this is a normal query", 0),
("select data from table", 0),
("just another harmless input", 0),
]
queries, labels = zip(*data)
# split data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(
queries, labels, test_size=0.2, random_state=42
)
# build a pipeline with a vectorizer and a logistic regression model
pipeline = make_pipeline(CountVectorizer(), LogisticRegression())
# train the model
pipeline.fit(X_train, y_train)
# save the model to a file
joblib.dump(pipeline, "model.pkl")
print("Model trained and saved to model.pkl")

7
server/Dockerfile Normal file
View file

@ -0,0 +1,7 @@
FROM alpine:latest
WORKDIR /app
COPY server /usr/local/bin/server
RUN chmod +x /usr/local/bin/server

2
server/build.sh Executable file
View file

@ -0,0 +1,2 @@
#!/bin/bash
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o server main.go

11
server/docker-compose.yml Normal file
View file

@ -0,0 +1,11 @@
services:
cspj-server:
image: cspj-server
container_name: cspj-server
restart: unless-stopped
network_mode: host
depends_on:
- postgres
volumes:
- "/home/vomitblood/build/cspj-application/docker/chungus/logs/:/tmp"
command: ["server", "-l", "/tmp"]

View file

@ -0,0 +1,24 @@
package cli
import (
"flag"
"fmt"
"os"
"github.com/Vomitblood/cspj-application/server/internal/config"
)
func GetFlags() {
logDir := flag.String("l", "", "Path to the log directory")
flag.Parse()
// check if the -l flag is provided
if flag.Lookup("l").Value.String() == "" {
fmt.Println("Error: The -l flag (log directory) is required.")
os.Exit(1)
}
// store the value in a globally accessible config package
config.LogDirectory = *logDir
}

View file

@ -0,0 +1,3 @@
package config
var LogDirectory string

View file

@ -87,6 +87,7 @@ func SetupDemoDb(w http.ResponseWriter, r *http.Request) {
// avoid duplicate entries and specify roles
insertDataSQL := `
INSERT INTO users (email, password, role) VALUES
('asdf@gmail.com', 'asdf', 'user'),
('alice@example.com', 'asdfalicepassword', 'user'),
('bob@example.com', 'asdfbobpassword', 'user'),
('charlie@example.com', 'asdfcharliepassword', 'admin')
@ -152,7 +153,6 @@ func FetchEmails() (map[string]bool, error) {
emails[email] = true
}
log.Println("Fetched emails:", emails)
return emails, nil
}

View file

@ -3,16 +3,17 @@ package log_backup
import (
"fmt"
"log"
"path/filepath"
"github.com/Vomitblood/cspj-application/server/internal/config"
"github.com/Vomitblood/cspj-application/server/internal/webdav"
"github.com/studio-b12/gowebdav"
)
// TODO: use values from config file
var localLogPaths = []string{
"/home/vomitblood/build/cspj-application/docker/chungus/logs/host-fs-accesslog.log",
"/home/vomitblood/build/cspj-application/docker/chungus/logs/host-fs-auditlog.log",
"/home/vomitblood/build/cspj-application/docker/chungus/logs/host-fs-errorlog.log",
filepath.Join(config.LogDirectory, "host-fs-accesslog.log"),
filepath.Join(config.LogDirectory, "host-fs-auditlog.log"),
filepath.Join(config.LogDirectory, "host-fs-errorlog.log"),
}
var remoteFiles = []string{

View file

@ -5,17 +5,14 @@ import (
"encoding/json"
"log"
"os"
"path/filepath"
"github.com/Vomitblood/cspj-application/server/internal/config"
"github.com/Vomitblood/cspj-application/server/internal/telegram"
"github.com/fsnotify/fsnotify"
tg "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
const (
// TODO: add config for user to specify log location
modsecLogFile = "/home/vomitblood/build/cspj-application/docker/chungus/logs/host-fs-auditlog.log"
)
var lastReadPosition int64 = 0
type LogEntry struct {
@ -25,16 +22,18 @@ type LogEntry struct {
}
func WatchFile(bot *tg.BotAPI) {
modsecLogFile := filepath.Join(config.LogDirectory, "host-fs-auditlog.log")
log.Println("Watching logfile directory:", config.LogDirectory)
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal("Failed to initialize watcher:", err)
log.Fatal("Failed to initialize watcher: ", err)
}
defer watcher.Close()
// add log file to watcher
err = watcher.Add(modsecLogFile)
if err != nil {
log.Fatal("Failed to watch log file:", err)
log.Fatal("Failed to watch log file: ", err)
}
log.Println("Monitoring log file for changes...")
@ -54,15 +53,16 @@ func WatchFile(bot *tg.BotAPI) {
if !ok {
return
}
log.Println("Watcher error:", err)
log.Println("Watcher error: ", err)
}
}
}
func readNewLines(bot *tg.BotAPI) {
modsecLogFile := filepath.Join(config.LogDirectory, "host-fs-auditlog.log")
file, err := os.Open(modsecLogFile)
if err != nil {
log.Println("Failed to reopen log file:", err)
log.Println("Failed to reopen log file: ", err)
return
}
defer file.Close()
@ -77,7 +77,7 @@ func readNewLines(bot *tg.BotAPI) {
// try to parse json
if err := json.Unmarshal([]byte(line), &logEntry); err != nil {
log.Println("⚠️ Failed to parse JSON:", err)
log.Println("⚠️ Failed to parse JSON: ", err)
// skip invalid json lines
// very crucial as modsecurity does not respect the json spec
continue
@ -93,6 +93,6 @@ func readNewLines(bot *tg.BotAPI) {
lastReadPosition, _ = file.Seek(0, os.SEEK_CUR)
if err := scanner.Err(); err != nil {
log.Println("Error reading log file:", err)
log.Println("Error reading log file: ", err)
}
}

View file

@ -1,6 +1,7 @@
package sql_injection
import (
"bytes"
"context"
"encoding/json"
"fmt"
@ -134,6 +135,7 @@ func SecureRegisterSql(w http.ResponseWriter, r *http.Request) {
}
func UnsecureLoginSql(w http.ResponseWriter, r *http.Request) {
log.Println("/unsecure-login-sql hit")
// decode the json body
var requestData struct {
Email string `json:"email"`
@ -175,12 +177,18 @@ func UnsecureLoginSql(w http.ResponseWriter, r *http.Request) {
// very secure login endpoint
func SecureLoginSql(w http.ResponseWriter, r *http.Request) {
// decode the json body
log.Println("/secure-login-sql hit")
var requestData struct {
Email string `json:"email"`
Password string `json:"password"`
}
type AIResponse struct {
Confidence float64 `json:"confidence"`
Query string `json:"query"`
Result string `json:"result"`
}
// declare new json decoder with custom property
jsonDecoder := json.NewDecoder(r.Body)
// rejects any unknown fields in the json, more strict
@ -214,6 +222,35 @@ func SecureLoginSql(w http.ResponseWriter, r *http.Request) {
return
}
// make insecure sql command just to send to ai model to check
insecureQuery := fmt.Sprintf("SELECT id, email, password FROM users WHERE email = '%s' AND password = '%s'", requestData.Email, requestData.Password)
// send the query to the ai model server
aiReqBody, _ := json.Marshal(map[string]string{"query": insecureQuery})
aiResp, err := http.Post("http://127.0.0.1:5000/predict", "application/json", bytes.NewBuffer(aiReqBody))
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
log.Println("Failed to reach AI model:", err)
return
}
defer aiResp.Body.Close()
var aiResponse AIResponse
if err := json.NewDecoder(aiResp.Body).Decode(&aiResponse); err != nil {
http.Error(w, "Failed to decode AI response", http.StatusInternalServerError)
log.Println("Error decoding AI response:", err)
return
}
log.Println("AI Response:", aiResponse.Result)
// reject the original request if ai result is fail
if aiResponse.Result == "fail" {
http.Error(w, "Potential SQL injection detected", http.StatusForbidden)
log.Println("AI detected SQL injection attempt")
return
}
// retrieve the email and password from the database
var id int
var email string

View file

@ -3,6 +3,8 @@ package main
import (
"log"
"github.com/Vomitblood/cspj-application/server/internal/cli"
"github.com/Vomitblood/cspj-application/server/internal/config"
"github.com/Vomitblood/cspj-application/server/internal/db"
"github.com/Vomitblood/cspj-application/server/internal/http_server"
"github.com/Vomitblood/cspj-application/server/internal/log_watcher"
@ -11,6 +13,9 @@ import (
)
func main() {
cli.GetFlags()
log.Printf("Log directory set to: %s", config.LogDirectory)
var err error
db.DbPool, err = db.ConnectToDb()
if err != nil {
@ -27,7 +32,5 @@ func main() {
// start log watcher
go log_watcher.WatchFile(tgBot)
//
http_server.ServeApi()
}