Skip to content
Open

Said #24

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
48 changes: 48 additions & 0 deletions Fas3-Granskning
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Säkerhetsgranskning: Analys av Automatiserade Fynd

Denna rapport sammanställer säkerhetsbrister identifierade via de automatiserade verktygen **GitHub Dependabot** (tredjepartsberoenden) och **CodeQL** (statisk kodanalys/SAST av källkod) i `backend/server.js`.

---

## 1. Sammanfattning av fynd

| Verktyg | Komponent / Plats | Allvarlighetsgrad | Huvudsaklig risk |
| :--- | :--- | :--- | :--- |
| **CodeQL** | `server.js` (L29-169) | 🔴 High | Saknad Rate Limiting (Sårbarhet mot DoS/Brute Force) |
| **CodeQL** | `server.js` (L20) | 🟡 Medium | Permissive CORS (Vidöppen policy, `*`) |
| **Dependabot** | Paket: `jsonwebtoken` | 🔴 High / Moderate | Signaturförfalskning och obehörig autentisering |
| **Dependabot** | Paket: `node-tar` | 🔴 High | Path Traversal (Obehörig filskrivning) |
| **Dependabot** | Paket: `qs` | 🟡 Moderate | Fjärrstyrd DoS via applikationskrasch |

---

## 2. Detaljerad analys

### Källkod: Saknad Rate Limiting (CodeQL: High)
CodeQL identifierade 7 unika fall i `server.js` där applikationen saknar hastighetsbegränsningar för inkommande anrop mot känsliga endpoints (t.ex. `/login`, `/register`).
* **Konsekvens:** Applikationen är sårbar för automatiserade *Brute Force*-attacker (lösenordsgissning) samt Denial of Service (DoS) där servern överbelastas av för många anrop.

### Källkod: Tillåtande CORS-konfiguration (CodeQL: Medium)
CORS är inställt på dafult-värdet wildcard (`origin: "*"`).
* **Konsekvens:** Servern litar på anrop från valfri extern domän, vilket öppnar upp för Cross-Origin-attacker där skadliga webbplatser i teorin kan göra obehöriga API-anrop i bakgrunden.

### Tredjepart: Sårbarheter i `jsonwebtoken` (Dependabot: High)
* **Konsekvens:** Tillåter en angripare att manipulera nyckeltyper (t.ex. tvinga fram ett byte från RSA till HMAC) för att förfalska giltiga tokens och helt kringgå autentiseringen på servern.

### Tredjepart: Sökvägssabotage i `node-tar` (Dependabot: High)
* **Konsekvens:** Paketet är sårbart för *Path Traversal* via djupt nästlade symlinks, vilket möjliggör för en angripare att skriva eller skriva över filer utanför applikationens rotkatalog.

---

## 3. Konkreta åtgärdsförslag

1. **Implementera rate-limiting i källkoden:**
Installera `express-rate-limit` och applicera på auth-endpoints i `server.js`:

2. Härda CORS-policyn:
Ersätt * på rad 20 med en explicit vitlista:
JavaScript
app.use(cors({ origin: '[https://din-frontend-url.com](https://din-frontend-url.com)' }));

3. Uppdatera sårbara bibliotek:
Kör npm audit fix i backend-katalogen för att lyfta jsonwebtoken och node-tar till säkra versioner.
253 changes: 253 additions & 0 deletions SÄKERHETSGRANSKNING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
# Säkerhetsgranskning - Analys av Identifierade Risker

## Sammanfattning
Denna granskning jämför de säkerhetsrisker som identifierades i planeringsfasen med den faktiska kodbasen. **KRITISKA BRISTER UPPTÄCKTA** som kan leda till allvarliga säkerhetsproblem.

---

## Hot 1: SPOOFING (Identitetsförfalskning) ⚠️ DELVIS FÖREKOMMANDE

### Identifierad Risk
- Bristfälliga tokens eller användar ID i klartext i URL:en

### Analys av Kod

✅ **KORREKT IMPLEMENTERAT:**
- Tokens skickas via Authorization headers (`Bearer ${token}`), INTE i URL
- JWT tokens använder hemlig JWT_SECRET för signering
- Tokens har begränsad livslängd (2 timmar)

❌ **SÄKERHETSBRIST FUNNEN:**
- **Token lagras i frontend state/localStorage**: Fil: `frontend/src/App.jsx`. `user.response.accessToken` lagras direkt i state.
- **Token exponerad i console.log**: Fil: `frontend/src/components/PostMessage.jsx`, rad 19. Loggar token till console: `console.log("Token being sent:", user?.response?.accessToken)`
- **Tokens lagras inte i HTTP-only cookies**: Fil: `backend/server.js` (Route: `POST /login`). Token returneras via JSON och kan läckas via XSS-attacker eller av JavaScript.


### Risk
- Tokens kan stoppas av XSS-attacker
- Tokens kan läckas vid datorbyte (de finns i browser history/localStorage)
- Loggning av tokens exponerar dem i DevTools

---

## Hot 2: TAMPERING (Manipulering av Data) 🔴 KRITISK SÅRBARHET

### Identifierad Risk
- Obehörig kan ändra eller radera data i databasen genom att manipulera API-anrop

### Analys av Kod

❌ **KRITISK SÄKERHETSBRIST - DELETE ROUTE UTAN AUTENTISERING:**

```javascript
// Fil: backend/server.js, rad 157-165
app.delete("/messages/:id", async (req, res) => {
if (!isValidId(req.params.id)) return res.status(400).json({ error: "Invalid message ID" })
try {
const message = await Message.findById(req.params.id)
if (!message) return res.status(404).json({ error: "Message not found" })
await message.deleteOne() // ← VEM SOM HELST KAN RADERA!
res.status(204).send()
```

**INGEN `authenticateUser` MIDDLEWARE!**

- Jämfört med PATCH-rouet som HAS auth:
```javascript
app.patch("/messages/:id", authenticateUser, async (req, res) => {
// ... verifierar ägarskap ...
if (message.user.toString() !== req.user._id.toString()) {
return res.status(403).json({ error: "You can only edit your own messages" })
}
```

### Risk
- **VILKEN SOM HELST kan radera VILKET MEDDELANDE SOM HELST**
- Ingen behörighetskontroll
- Möjlighet till dataförstöring och sabotage
- DELETE button i SingleMessage.jsx skickar request utan att verifiera ägarskap på servern

### Bevissäkerhet
- Frontend-kontroll: Fil: `frontend/src/components/SingleMessage.jsx`, rad 14: `const isOwner = user && user.response.id === message.user?._id`
- Detta är ENDAST UI-granskning - servern validerar aldrig ägarskapet
- Frontend kan enkelt manipuleras med DevTools

---

## Hot 3: INFORMATION DISCLOSURE (Informationsläcka) 🟡 DELVIS FÖREKOMMANDE

### Identifierad Risk
- Lösenord läcks i klartext
- Meddelanden läcks till obehöriga

### Analys av Kod

✅ **KORREKT IMPLEMENTERAT:**
- Lösenord hasheras med bcrypt 10-rounds: `bcrypt.hash(password, 10)`
- Lösenord aldrig returnerat i responses
- Kommunikation över HTTPS (Render.com)

❌ **SÄKERHETSBRIST:**
- **CORS öppet för alla origins**: Fil: `backend/server.js`, rad 9.
```javascript
app.use(cors({
origin: "*", // ← VILKEN SOM HELST domän kan göra requests!
}))
```

- **Error messages exponerar information**:
```javascript
// server.js - Login endpoint
if (!user) {
return res.status(401).json({
success: false,
message: "No account found with that username or email", // Avslöjar om email existerar!
```

- **Användar ID exponeras i meddelanden**: Alla användar IDs är synliga (ObjectIds är inte hemliga men kan vara känslig info)

### Risk
- CSRF-attacker från vilken webbsida som helst
- Brute-force login med information om vilka emails som är registrerade
- Cross-origin API-missbruk

---

## Hot 4: DENIAL OF SERVICE (Överbelastning) 🔴 KRITISK BRIST

### Identifierad Risk
- Ingen rate limiting
- Server kan överbelastas av meddelanden eller inloggningsförsök

### Analys av Kod

❌ **INGEN RATE LIMITING IMPLEMENTERAT**

- **Login endpoint saknar skydd mot brute-force**:
```javascript
app.post("/login", async (req, res) => {
// Ingen kontroll på antalet försök per IP/användare
```

- **Register endpoint saknar skydd**:
```javascript
app.post("/register", async (req, res) => {
// Ingen rate limiting - kan skapa obegränsade accounts
```

- **POST /messages saknar längdbegränsning**:
```javascript
app.post("/messages", authenticateUser, async (req, res) => {
const message = new Message({ message: req.body.message, user: req.user._id })
// Inget check på req.body.message längd!
```

- **Ingen validering av lösenordslängd**:
```javascript
// Register-endpoint verifierar username men NOT password length
if (!username || username.trim().length < 2) {
// men ingen check för password!
```

### Risk
- Brute-force login: Attacker kan prova lösenord utan begränsning
- Spam av meddelanden: En användare kan skriva miljontals meddelanden
- Stora meddelanden: Kan skicka mycket stora payloads
- Account enumeration: Kan skapa spam-accounts obegränsad
- DoS-attack genom överflöd av requests

---

## Hot 5: ELEVATION OF PRIVILEGE (Behörighetsvinst) 🔴 KRITISK BRIST

### Identifierad Risk
- Vanlig användare får administratörsrättigheter eller åtkomst till obehörig funktioner

### Analys av Kod

❌ **KRITISK SÅRBARHET - DELETE UTAN AUKTORISERING**

Samma som Tampering-problemet: DELETE-rouet har ingen autentisering och ingen behörighetskontroll.

- Vilken inloggad användare som helst kan radera VILKEN meddelande som helst
- En oauthentiserad användare kan potentiellt radera meddelanden (om CORS+frontend manipulation)

❌ **INGEN ROLL-BASERAD ÅTKOMSTKONTROLL**

- Ingen skillnad mellan "admin" och vanlig användare
- Alla har samma behörigheter
- Ingen möjlighet att implementera moderatörer eller administrators

### Risk
- Användare A kan radera Användare B:s meddelanden
- Möjlighet till vandalism och dataskada
- Ingen möjlighet att designa ett admin-system i framtiden

---

## Säkerhetskrav Analys

### Krav 1: Lösenordshantering och kryptering ✅ LÖST
- ✅ Lösenord hasheras med bcrypt
- ✅ Aldrig i klartext i DB eller responses
- ✅ Aldrig i klartext mellan komponenter (HTTPS)

**Status: GODKÄNT**

---

### Krav 2: Auktorisering för meddelanden ❌ DELVIS LÖST
- ✅ PATCH route kontrollerar ägarskap
- ❌ DELETE route kontrollerar INTE ägarskap - **KRITISK BRIST**
- ❌ Frontend-validering är INTE tillräcklig

**Status: UNDERKÄNT - Måste fixas innan produktion**

---

### Krav 3: Indatavalidering ❌ OFULLSTÄNDIG
- ✅ Username validering: minlängd 2
- ✅ Email format (men minimal validering)
- ✅ ObjectId validering på API
- ❌ Meddelandelängd - ingen validering
- ❌ Lösenordslängd - ingen validering
- ❌ Ingen sanering av specialtecken eller potentiella injektioner

**Status: DELVIS GODKÄNT - Bör förbättras**

---

### Krav 4: Sessionshantering ❌ DÅLIGT IMPLEMENTERAT
- ✅ JWT tokens använd för sessioner
- ✅ Token-verifiering på protected routes
- ❌ Tokens lagras INTE säkert (inte HTTP-only cookies)
- ❌ Ingen refresh token mekanisme
- ❌ Ingen logout på servern (tokens kan inte invalideras)
- ❌ Token loggas till console

**Status: UNDERKÄNT - Token-hantering måste förbättras**

---

## Sammanfattning av Kritiska Brister

| Prioritet | Problem | Plats | Lösning |
|-----------|---------|-------|---------|
| 🔴 KRITISK | DELETE utan autentisering | server.js L157 | Lägg till `authenticateUser` middleware och ägarskapscheck |
| 🔴 KRITISK | Ingen rate limiting | Alla POST routes | Implementera rate-limit package |
| 🔴 KRITISK | CORS öppet för alla | server.js L9 | Begränsa till frontend URL |
| 🟡 HÖG | Tokens i localStorage | Frontend State | Använd HTTP-only cookies |
| 🟡 HÖG | Token i console.log | PostMessage.jsx L19 | Ta bort debug logging |
| 🟡 MEDEL | Ingen indatavalidering | POST /messages | Validera meddelandelängd |
| 🟡 MEDEL | Information disclosure | Login response | Ändra error messages |

---

## Rekommendationer

1. **OMEDELBAR**: Fixera DELETE-rouet - lägg till autentisering och auktorisering
2. **OMEDELBAR**: Implementera rate limiting på auth-routes
3. **SNART**: Begränsa CORS till frontend URL
4. **SNART**: Migrera tokens till HTTP-only cookies
5. **SENARE**: Implementera indatavalidering för alla inputs
6. **SENARE**: Lägg till refresh token mekanisme
2 changes: 1 addition & 1 deletion backend/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const userSchema = new mongoose.Schema({
lowercase: true,
},
password: {
type: String,
type: String, //Här borde det finnas mer krav på lösenordet, t.ex. minsta längd, krav på siffror/specialtecken etc.
required: true,
},
})
Expand Down
16 changes: 9 additions & 7 deletions backend/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ import listEndpoints from "express-list-endpoints"
if (!process.env.JWT_SECRET) throw new Error("JWT_SECRET is not set in .env")

const PORT = process.env.PORT || "3000"
const app = express()
const app = express() //Implementera rate-limiting, t.ex. express-rate-limit, för att skydda särskilt inloggningsendpointen mot brute-force attacker.
app.use(helmet())
app.use(cors({
origin: "*",
origin: "*", //DET HÄR ÄR SVÅRT ATT MOTIVERA. Backend behöver väl inte svara på anrop från andra domäner än frontend?
// Extra problematiskt då vi saknar rate-limiting och andra skydd mot missbruk.
}))
app.use(express.json())

Expand Down Expand Up @@ -73,7 +74,7 @@ app.post("/register", async (req, res) => {
}
})

app.post("/login", async (req, res) => {
app.post("/login", async (req, res) => { // INGEN RATE-LIMITING, VILKET GÖR DENNA ENDPOINT SÅRBAR FÖR BRUTE-FORCE ATTACKS.
try {
const { login, password } = req.body
const user = await User.findOne({
Expand All @@ -83,7 +84,7 @@ app.post("/login", async (req, res) => {
if (!user) {
return res.status(401).json({
success: false,
message: "No account found with that username or email",
message: "No account found with that username or email", //RISK FÖR INFORMATIONSLÄCKAGE. Behöver användaren veta detta?
response: null,
})
}
Expand All @@ -92,7 +93,7 @@ app.post("/login", async (req, res) => {
if (!passwordMatch) {
return res.status(401).json({
success: false,
message: "Password is incorrect",
message: "Password is incorrect", //RISK FÖR INFORMATIONSLÄCKAGE. Behöver användaren veta detta?
response: null,
})
}
Expand Down Expand Up @@ -136,7 +137,7 @@ app.get("/messages", async (req, res) => {
}
})

app.post("/messages", authenticateUser, async (req, res) => {
app.post("/messages", authenticateUser, async (req, res) => { // INGEN VALIDERING AV INNEHÅLLET I MEDDELANDET. VEM SOM HELST KAN SPAMMA VILKEN DATA SOM HELST, INKLUSIVE MALICIOUS SCRIPTS.
const message = new Message({ message: req.body.message, user: req.user._id })
try {
const saved = await message.save()
Expand Down Expand Up @@ -165,10 +166,11 @@ app.patch("/messages/:id", authenticateUser, async (req, res) => {
}
})

app.delete("/messages/:id", async (req, res) => {
app.delete("/messages/:id", async (req, res) => { //Varför körs inte authenticateUser här?
if (!isValidId(req.params.id)) return res.status(400).json({ error: "Invalid message ID" })
try {
const message = await Message.findById(req.params.id)
// INGEN AUTENSIERING GÖRS. VEM SOM HELST KAN RADERA MED RÄTT ID.
if (!message) return res.status(404).json({ error: "Message not found" })
await message.deleteOne()
res.status(204).send()
Expand Down
Loading