Skip to content

Commit a2aa860

Browse files
committed
Merge branch 'main' into fixed
2 parents df2e5e0 + 8dd37b2 commit a2aa860

File tree

10 files changed

+312
-79
lines changed

10 files changed

+312
-79
lines changed

.github/workflows/memorais.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ jobs:
3232
- name: remove docker volumes
3333
run: |
3434
cd service
35-
docker compose down
36-
docker volume rm -f service_mariadb_data service_mariadb service_qdrant_data service_qdrant
35+
docker compose down -v
3736
- name: start service
3837
run: |
3938
cd service
@@ -47,7 +46,7 @@ jobs:
4746
echo "ENOCHECKER_TEST_SERVICE_ADDRESS=$(ip -4 address show dev eth0 | grep inet | awk '{ print $2 }' | sed 's|/.*$||')" >> $GITHUB_ENV
4847
- name: run enochecker_test
4948
run: |
50-
sleep 20
49+
sleep 5
5150
enochecker_test
5251
- name: Dump docker logs on failure
5352
if: failure()

README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,34 @@ Flags, in contrast, are random character strings with no inherent semantics. To
2020

2121
Because a leaky endpoint exposes these embeddings, an attacker can download the embedding of any document (potentially one containing a flag), replicate the same encoding process locally, and use the resulting lookup table to recover every character of the flag.
2222

23+
### Attack Flow Chart
24+
25+
```mermaid
26+
flowchart TD
27+
A["🎯 Start: Get flag hint session ID"] --> B["👤 Register new attacker user"]
28+
B --> C["🔐 Login as attacker"]
29+
C --> D["📥 Import shared session containing flag"]
30+
D --> E["🔍 Create search query with random characters"]
31+
E --> F["📊 Search collection for documents"]
32+
F --> G["🔤 Build character embedding lookup table<br/>by embedding all ASCII characters"]
33+
G --> H["📄 For each found document:"]
34+
H --> I["📊 Extract embedding vector and norm"]
35+
I --> J["🧩 Reconstruct flag character by character<br/>using nearest neighbor matching"]
36+
J --> K{"🚫 Does flag end with 'FAKE'?"}
37+
K -->|Yes| L["⏭️ Skip this document"]
38+
K -->|No| M["✅ Unformat and return flag"]
39+
L --> N{"📝 More documents?"}
40+
N -->|Yes| H
41+
N -->|No| O["❌ No flag found"]
42+
M --> P["🏁 Success: Flag recovered"]
43+
44+
style A fill:#ffcccb
45+
style P fill:#90EE90
46+
style O fill:#ffcccb
47+
style K fill:#FFE4B5
48+
style G fill:#E6E6FA
49+
```
50+
2351
## Cryptographic Handshake Bypass (All-Zero IV Vulnerability)
2452
This vulnerability exists in the custom MCP (Model Context Protocol) authentication handshake mechanism for personal AI-agents. The service implements a challenge-response authentication system using AES CFB8 encryption, but contains a critical flaw in its cryptographic implementation.
2553

@@ -41,6 +69,34 @@ The attacker can repeatedly attempt the handshake by:
4169
3. Eventually succeeding in authentication due to the deterministic nature of the encryption with known inputs
4270
4. Once authenticated, gaining access to the MCP interface to read chat sessions and retrieve flags
4371

72+
### Attack Flow Chart
73+
74+
```mermaid
75+
flowchart TD
76+
A["🎯 Start: Get user ID and session ID<br/>from attack info"] --> B["🔢 Create all-zero client challenge<br/>(8 bytes of 0x00)"]
77+
B --> C["🔄 Start handshake attempts<br/>(up to 5000 tries)"]
78+
C --> D["📤 Send zero-byte client challenge<br/>to server"]
79+
D --> E["📥 Receive server challenge,<br/>memory key, and IV"]
80+
E --> F["⚠️ IV is all-zero due to vulnerability:<br/>var iv = new byte[16]"]
81+
F --> G["🔐 Attempt authentication with<br/>zero-byte client credentials"]
82+
G --> H{"✅ Authentication successful?"}
83+
H -->|No| I{"🔄 More attempts left?"}
84+
H -->|Yes| J["🎟️ Receive JWT token"]
85+
I -->|Yes| D
86+
I -->|No| K["❌ Attack failed after 5000 attempts"]
87+
J --> L["🤖 Connect to MCP interface<br/>using JWT token"]
88+
L --> M["📖 Read messages from<br/>target chat session"]
89+
M --> N["🏴 Extract flag from messages<br/>(message[1]['content'])"]
90+
N --> O["✅ Unformat and return flag"]
91+
O --> P["🏁 Success: Flag recovered"]
92+
93+
style A fill:#ffcccb
94+
style F fill:#ff6b6b
95+
style P fill:#90EE90
96+
style K fill:#ffcccb
97+
style H fill:#FFE4B5
98+
style I fill:#FFE4B5
99+
```
44100

45101
# enochecker_cli
46102

checker/src/checker.py

Lines changed: 187 additions & 58 deletions
Large diffs are not rendered by default.

checker/src/sentences.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,10 @@
1313
"cd /home && find . -name flag.txt",
1414
"select name, password from users",
1515
"touch /tmp/testfile",
16-
"//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++@===.[]//++++"
16+
"ENOMEqxNzhil07MZANu7Gq0d9cRSB/rN9c1mEWbmaCMKUArQq5X#################################################################################################################################################################################################################################################################################################################################################################################################################################################################",
17+
"@+++++++++[>++++++++>++++++>++++++++++++>+++++++++++>+++++++++>++++++++>+++++++++++++>++++++++++>+++++<<<<<<<<<-]>---.>>>>---.+.<<<++.>>>---.<<.+++++.>.>-.++++.<-.>+++++++.>>.<++++++.+.>+++.<<<<<<++.>>>>>>.++.<<<<-.<<-.>>-------.+++++.<<---.>>>>>>>.<<<.++.<<<---.>>>>.-----.>-.>.----.<<<<<+++.>>.>+++++.<<<--.>.>-.<+.>>>>>++.<<<<<++++.>-.++++.<<-.<<+++.>++.>>--.<<<--.>>>>-.>>.@PADDINGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
18+
"@++++++++[>+++++++++>+++++++++++>+++++++++>++++++>++++++++++++++>+++++++++++>+++++++<<<<<<<-]>---.>>++++++.+.<++.++++++++++..>+++++.<<++.>-.>>>---.<<------.>>++.<-.<--.>+++++.>+++++++++.<<<<-.---.>+++++++++.>>++.<+++++.------.>>>-.<.<-----.>>-.<+.<<<------.++++++++.>>--.<<-------.>+.<++++.<++.>>>>-.-----.<<<.>>+++++.>--.<.-----.>>.<<<<<+.>>>>>---.<<<<------.>>>>>+.-.<<<<<-.<-.>-.++++++++.@PADDINGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
19+
"@++++++++[>+++++++++>+++++++++>+++++++>++++++++++++++>+++++++>+++++++++++++++>++++++++>++++++++++++++<<<<<<<<-]>---.>++++++.+.-.+.+++++++.<++.>+.----.>-.<<--.>>>+.<<<++++++.>++.>-.>------.<<<-----.+++.>>>++.-.------.<------.>----.>.>>++++.<<<++++.<<<+.+.>---.>>>.<---.>>>.<---.>++++.+.<<.<.<<+.>>>>+++++.>------.<<<<+.>>>>>.<<<<<+.>>>>++.<<<<<+.<---.>>>>>.<<<<++++.<+++++++.>-.<+++.@PADDINGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
20+
"@++++++++[>+++++++++>+++++++++++++>++++++++>+++++>+++++++++++++++>+++++++++>++++++>+++++++++++++<<<<<<<<-]>---.+++++++++.+.>+++.++++.<++++.>>+.<<.>>>+++.>>>>-.<<<--.<<<<++++.>>>>>>>-.<<<<<<<.>>>>>>>---.<<++.>++.+++++++.-----.>--.<.<<-.<.>>>>+++.<<---.<+++.<<<<.++.>>>>>++.<<++++.>>>>--.+.<---.<<<<<<.>>>++++++.>>---.<<++.----.<<++.<.>>>--.>++.<+++.<<-------.>>+.>>>>-.-.++.<<+.++.<<<<<----.@PADDINGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
21+
"@+++++++++[+++++++++>++++++++++++>++++++>++++++++++>++++++++>++++++++++++>+++++>+++++++++++>+++++++++++++<<<<<<<<<-]>---.+++++++++.+.>++++.<----.>+.+++++.>----.>----.>----.<<<++++.>>>>-.<<.<<<-.--.---.>--.<++++++++.>>>>>>--.<<<++.<<----.>-.>--.---.--.<<----.>>+++++++.<<<++++.>>>>+++.>>>.>+.-.<<<--.>>>-.<<<<----.>>>>++++++.----.+++.<<<<+++++++.+++++.<<<+.>>>>>>-.>-.<<<<<-.<++++.<<.--.>>>.<<+++++.>>---.>----.@PADDINGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
1722
]

service/backend/Data/sentences.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
"Timeless ideas remain relevant across generations.",
4949
"Chronology helps us understand the sequence of events.",
5050
"Temporal changes affect all living things.",
51-
"+-<>[].,abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+/=#@",
5251
"The flag of Germany is black, red, and gold.",
5352
"He raised the white flag in surrender.",
5453
"Ships fly a flag to get their nationality.",
@@ -98,5 +97,6 @@
9897
"The flag on the tail of the aircraft was Dutch.",
9998
"Flag iris bloomed early this year.",
10099
"The team carried the national flag onto the field.",
101-
"He carved his initials into a piece of flagstone."
100+
"He carved his initials into a piece of flagstone.",
101+
"!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
102102
]

service/docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ services:
1313
deploy:
1414
resources:
1515
limits:
16-
cpus: '0.15'
16+
cpus: '0.45'
1717
memory: 100M
1818
networks:
1919
- memorais_network
@@ -91,7 +91,7 @@ services:
9191
deploy:
9292
resources:
9393
limits:
94-
cpus: '1.65'
94+
cpus: '1.35'
9595
memory: 650M
9696
networks:
9797
- memorais_network

service/frontend/nginx.conf

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,54 @@
1-
events { worker_connections 1024; }
1+
worker_processes auto;
2+
3+
events {
4+
use epoll;
5+
worker_connections 2048;
6+
multi_accept on;
7+
}
28

39
http {
410
include mime.types;
511
default_type application/octet-stream;
6-
sendfile on;
7-
keepalive_timeout 65;
12+
13+
sendfile on;
14+
tcp_nopush on;
15+
tcp_nodelay on;
16+
keepalive_timeout 30;
17+
types_hash_max_size 2048;
18+
19+
gzip on;
20+
gzip_disable "msie6";
21+
gzip_vary on;
22+
gzip_proxied any;
23+
gzip_comp_level 3;
24+
gzip_buffers 16 8k;
25+
gzip_http_version 1.1;
26+
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
27+
828

929
server {
1030
listen 80;
1131
server_name localhost;
32+
access_log off;
1233

1334
location / {
1435
root /usr/share/nginx/html;
1536
index index.html;
1637
try_files $uri $uri/ /index.html;
38+
expires 1d;
39+
add_header Cache-Control "public";
1740
}
1841

1942
location /api {
2043
proxy_pass http://backend:5000;
21-
proxy_http_version 1.1;
22-
proxy_set_header Upgrade $http_upgrade;
23-
proxy_set_header Connection keep-alive;
2444
proxy_set_header Host $host;
25-
proxy_cache_bypass $http_upgrade;
45+
proxy_http_version 1.1;
2646
}
2747

2848
location /mcp {
2949
proxy_pass http://backend:5000;
30-
proxy_http_version 1.1;
31-
proxy_set_header Upgrade $http_upgrade;
32-
proxy_set_header Connection keep-alive;
3350
proxy_set_header Host $host;
34-
proxy_cache_bypass $http_upgrade;
51+
proxy_http_version 1.1;
3552
}
3653
}
3754
}

service/frontend/src/App.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ function App() {
2121
const [showImportError, setShowImportError] = useState(false);
2222
const [importErrorMessage, setImportErrorMessage] = useState('');
2323
const [abortController, setAbortController] = useState(null);
24+
const [showShareSuccess, setShowShareSuccess] = useState(false);
25+
const [sharedSessionId, setSharedSessionId] = useState('');
2426

2527
const loadSessions = async () => {
2628
try {
@@ -212,6 +214,8 @@ function App() {
212214
try {
213215
await shareChatSession(sessionId);
214216
setSharedSessions(prev => ({ ...prev, [sessionId]: true }));
217+
setSharedSessionId(sessionId); // Store the ID of the shared session
218+
setShowShareSuccess(true);
215219
} catch (e) {
216220
if (e.status === 401) {
217221
setIsAuthenticated(false);
@@ -312,6 +316,25 @@ function App() {
312316
</div>
313317
</div>
314318
)}
319+
{showShareSuccess && (
320+
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
321+
<div className="bg-gray-800 p-6 rounded-lg shadow-lg w-full max-w-md">
322+
<h2 className="text-lg font-bold text-white mb-4">Session Shared</h2>
323+
<p className="text-white mb-4">Session with ID: {sharedSessionId} has been shared. Users can now access it by importing the session ID.</p>
324+
<div className="flex justify-end">
325+
<button
326+
className="px-4 py-2 rounded bg-blue-600 text-white hover:bg-blue-700"
327+
onClick={() => {
328+
setShowShareSuccess(false);
329+
setSharedSessionId('');
330+
}}
331+
>
332+
OK
333+
</button>
334+
</div>
335+
</div>
336+
</div>
337+
)}
315338
</div>
316339
);
317340
}
-1.13 MB
Loading

service/frontend/src/components/Sidebar.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,17 @@ function Sidebar({ sessions, importedSessions, onSelect, onCreate, selectedSessi
4141
<div
4242
key={session.id}
4343
className={`p-4 cursor-pointer hover:bg-gray-800 ${selectedSessionId === session.id ? 'bg-gray-800 font-bold' : ''}`}
44+
onClick={() => onSelect(session.id)}
4445
>
4546
<div className="flex items-center justify-between">
46-
<span onClick={() => onSelect(session.id)} className="flex-1 cursor-pointer">
47+
<span className="flex-1">
4748
Session {session.id}
4849
</span>
4950
<button
50-
onClick={() => onShare(session.id)}
51+
onClick={(e) => {
52+
e.stopPropagation();
53+
onShare(session.id);
54+
}}
5155
className={`ml-2 ${sharedSessions && sharedSessions[session.id] ? 'bg-gray-600 cursor-not-allowed' : 'bg-green-600 hover:bg-green-700'} text-white px-2 py-1 rounded text-xs`}
5256
title="Share session - Makes this session universally available to all other users via its ID"
5357
disabled={sharedSessions && sharedSessions[session.id]}

0 commit comments

Comments
 (0)