Try   HackMD

[Firmware Version]:1.01

[Vulnerability Type]:buffer overflow

[Vulnerability Summary]:

The software component /htdocs/web/captcha.cgi suffers from a buffer overflow vulnerability due to an unsafe implementation of the strncpy() function. When a user-supplied input string (a2) that is exactly 64 characters long (or longer) without a NULL termination character is provided, the strncpy(a1 + 8, a2, 0x40u); operation fails to append a NULL character at the end of the copied string. This oversight leads to a potential buffer overflow condition.

[Discoverer]:

Jian-Xian Li, Yen-Jen Lin, Hung-Min Sun / Laboratory of Information Security, National Tsing Hua University

[Crash Result]:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →


[Vulnerbility Detail]:

Function_name (Problematic Code)

  • main (v8 = (int (*)())&captchacgi_main;)
int __cdecl main(int argc, const char **argv, const char **envp)
{
  const char *v3; // $s0
  char *v6; // $v0
  int (*v8)(); // $t9
  int v9; // $a0

  v3 = *argv;
  v6 = strrchr(*argv, 47);
  if ( v6 )
    v3 = v6 + 1;
  if ( !strcmp(v3, "phpcgi") )
  {
    v8 = phpcgi_main;
    v9 = argc;
    return ((int (__fastcall *)(int, const char **, const char **))v8)(v9, argv, envp);
  }
  if ( !strcmp(v3, "dlcfg.cgi") )
  {
    v8 = dlcfg_main;
    v9 = argc;
    return ((int (__fastcall *)(int, const char **, const char **))v8)(v9, argv, envp);
  }
  if ( !strcmp(v3, "seama.cgi") )
  {
    v8 = seamacgi_main;
    v9 = argc;
    return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
  }
  if ( !strcmp(v3, "fwup.cgi") )
  {
    v8 = fwup_main;
    v9 = argc;
    return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
  }
  if ( !strcmp(v3, "fwupdater") )
  {
    v8 = (int (*)())fwupdater_main;
    v9 = argc;
    return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
  }
  if ( !strcmp(v3, "session.cgi") )
  {
    v8 = (int (*)())&sessioncgi_main;
    v9 = argc;
    return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
  }
  if ( !strcmp(v3, "captcha.cgi") )
  {
    v8 = (int (*)())&captchacgi_main; //Execute captcha.cgi, enter the corresponding function.

    v9 = argc;
    return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
  }
  if ( !strcmp(v3, "hedwig.cgi") )
  {
    v8 = hedwigcgi_main;
    v9 = argc;
    return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
  }
  if ( !strcmp(v3, "pigwidgeon.cgi") )
  {
    v8 = (int (*)())&pigwidgeoncgi_main;
    v9 = argc;
    return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
  }
  if ( !strcmp(v3, "service.cgi") )
  {
    v8 = (int (*)())&servicecgi_main;
    v9 = argc;
    return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
  }
  if ( !strcmp(v3, "ssdpcgi") )
  {
    v8 = ssdpcgi_main;
    v9 = argc;
    return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
  }
  if ( !strcmp(v3, "soap.cgi") )
  {
    v8 = soapcgi_main;
    v9 = argc;
    return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
  }
  if ( !strcmp(v3, "gena.cgi") )
  {
    v8 = genacgi_main;
    v9 = argc;
    return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
  }
  if ( !strcmp(v3, "conntrack.cgi") )
  {
    v8 = (int (*)())&conntrackcgi_main;
    v9 = argc;
    return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
  }
  if ( !strcmp(v3, "hnap") )
  {
    v8 = (int (*)())&hnap_main;
    v9 = argc;
    return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
  }
  printf("CGI.BIN, unknown command %s\n", v3);
  return 1;
}
  • captchacgi_main (captcha = sess_generate_captcha((int)v9);)
int captchacgi_main()
{
  char *v0; // $v0
  const char *v1; // $s0
  const char *v2; // $a2
  int (*v3)(); // $a0
  int v4; // $a2
  int captcha; // $v0
  int v6; // $s1
  int v7; // $s0
  char v9[216]; // [sp+18h] [-1ECh] BYREF 
  char v10[16]; // [sp+F0h] [-114h] BYREF
  char v11[260]; // [sp+100h] [-104h] BYREF

  v0 = getenv("REQUEST_METHOD");
  v1 = v0;
  if ( !v0 )
  {
    v2 = "no REQUEST";
LABEL_10:
    cgibin_print_http_status(400, "", v2);
    v7 = -1;
    goto LABEL_12;
  }
  if ( !strcasecmp(v0, "GET") )
  {
    v3 = sub_408CC4;
    v4 = 64;
  }
  else
  {
    if ( strcasecmp(v1, "POST") )
    {
      v2 = "unsupported HTTP request";
      goto LABEL_10;
    }
    v3 = sub_408C10;
    v4 = 1024;
  }
  cgibin_parse_request(v3, 0, v4);
  captcha = sess_generate_captcha((int)v9); //generate captcha
  v6 = captcha;
  if ( captcha )
  {
    sprintf(v11, "rndimage -f /htdocs/web/docs/captcha_%d.jpeg -p /usr/sbin/fonts -w 180 -t 40 %s", captcha, v10);
    v7 = 0;
    system(v11);
    printf(
      "HTTP/1.1 200 OK\r\n"
      "Content-Type: text/xml\r\n"
      "\r\n"
      "<?xml version=\"1.0\" encoding=\"utf-8\"?><captcha><result>OK</result><message>/docs/captcha_%d.jpeg</message></captcha>",
      v6);
  }
  else
  {
    printf(
      "HTTP/1.1 200 OK\r\n"
      "Content-Type: text/xml\r\n"
      "\r\n"
      "<?xml version=\"1.0\" encoding=\"utf-8\"?><captcha>\n"
      "\t<result>FAIL</result><message>NO SESSION</message>\n"
      "</captcha>");
    v7 = 0;
  }
LABEL_12:
  sub_4089C0();
  return v7;
}
  • sess_generate_captcha (sess_get_uid(v3);、v2 = sub_40795C(a1, string);)
int __fastcall sess_generate_captcha(int a1)
{
  int v2; // $s1
  int v3; // $s2
  int string; // $v0
  int i; // $s0
  unsigned int v6; // $v0
  int v7; // $v1
  int v9[4]; // [sp+18h] [-70h] BYREF
  char v10[28]; // [sp+28h] [-60h] BYREF
  struct sysinfo seed; // [sp+44h] [-44h] BYREF

  strcpy(v10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
  v2 = 0;
  v3 = sobj_new();
  sub_40787C();
  sub_4072D0(v9);
  if ( v3 )
  {
    sess_get_uid(v3);   //It does not limit the cookie value input by the user, thus being a point of attack.
    string = sobj_get_string(v3); //The provided value is converted to a string, but the size of the v3 value is not limited either.
    v2 = sub_40795C(a1, string); //The value of the string will be very large and long, which is then passed into the sub_40795C function.
    if ( v2 )
    {
      sysinfo(&seed);
      srandom(seed.uptime);
      for ( i = 0; i != 6; ++i )
      {
        v6 = random();
        v7 = a1 + i;
        *(_BYTE *)(v7 + 216) = *((_BYTE *)&v9[4] + v6 % 0x1A);
      }
      *(_DWORD *)(a1 + 4) = -1;
      *(_BYTE *)(a1 + 222) = 0;
      sysinfo(&seed);
      *(_DWORD *)a1 = seed.uptime + v9[0];
      sub_407660(a1, v2, 1);
    }
    sobj_del(v3);
  }
  return v2;
}
  • sess_get_uid
int __fastcall sess_get_uid(int a1)
{
  int v2; // $s2
  char *v3; // $v0
  int v4; // $s3
  char *v5; // $s4
  int v6; // $s1
  int v7; // $s0
  char *string; // $v0
  int result; // $v0

  v2 = sobj_new();
  v4 = sobj_new();
  v3 = getenv("HTTP_COOKIE");  //After getenv("HTTP_COOKIE"); reads the cookie value, it does not restrict the size and length of this value.
  if ( !v2 )
    goto LABEL_27;
  if ( !v4 )
    goto LABEL_27;
  v5 = v3;
  if ( !v3 )
    goto LABEL_27;
  v6 = 0;
  while ( 1 )
  {
    v7 = *v5;
    if ( !*v5 )
      break;
    if ( v6 == 1 )
      goto LABEL_11;
    if ( v6 < 2 )
    {
      if ( v7 == 32 )
        goto LABEL_18;
      sobj_free(v2);
      sobj_free(v4);
LABEL_11:
      if ( v7 == 59 )
      {
        v6 = 0;
      }
      else
      {
        v6 = 2;
        if ( v7 != 61 )
        {
          sobj_add_char(v2, v7);
          v6 = 1;
        }
      }
      goto LABEL_18;
    }
    if ( v6 == 2 )
    {
      if ( v7 == 59 )
      {
        v6 = 3;
        goto LABEL_18;
      }
      sobj_add_char(v4, *v5++);
    }
    else
    {
      v6 = 0;
      if ( !sobj_strcmp(v2, "uid") )
        goto LABEL_21;
LABEL_18:
      ++v5;
    }
  }
  if ( !sobj_strcmp(v2, "uid") )
  {
LABEL_21:
    string = (char *)sobj_get_string(v4);
    goto LABEL_22;
  }
LABEL_27:
  string = getenv("REMOTE_ADDR");
LABEL_22:
  result = sobj_add_string(a1, string);
  if ( v2 )
    result = sobj_del(v2);
  if ( v4 )
    return sobj_del(v4);
  return result;
} 
  • sub_40795C (strncpy(a1 + 8, a2, 0x40u);)
int __fastcall sub_40795C(char *a1, const char *a2)  
{
  int i; // $s1
  unsigned int v5; // $s0
  unsigned int v6; // $s0
  int v8; // [sp+18h] [-54h] BYREF
  int v9; // [sp+1Ch] [-50h]
  struct sysinfo v10; // [sp+28h] [-44h] BYREF

  if ( !a1 )
    return 0;
  i = 0;
  if ( a2 && *a2 )
  {
    sub_4072D0(&v8);
    memset(a1, 0, 0xE8u);
    for ( i = 1; v9 >= i; ++i )
    {
      if ( !sub_407660(a1, i, 0) )
      {
        v5 = *(_DWORD *)a1;
        sysinfo(&v10);
        if ( v5 >= v10.uptime && !strcmp(a1 + 8, a2) )
          return i;
      }
    }
    i = 1;
    while ( v9 >= i )
    {
      if ( sub_407660(a1, i, 0) )
        goto LABEL_14;
      v6 = *(_DWORD *)a1;
      sysinfo(&v10);
      ++i;
      if ( v6 < v10.uptime )
      {
        --i;
LABEL_14:
        memset(a1, 0, 0xE8u);
        sysinfo(&v10);
        *(_DWORD *)a1 = v10.uptime + v8;
        *((_DWORD *)a1 + 1) = -1;
        strncpy(a1 + 8, a2, 0x40u);  //Vulnerbility code
        sub_407660(a1, i, 1);
        return i;
      }
    }
    return 0;
  }
  return i;
}

The line strncpy(a1 + 8, a2, 0x40u); in the program creates a buffer overflow vulnerability due to improper usage of the strncpy() function.

However, if a2 is 64 characters or longer and does not contain a NULL termination character ('\0'), strncpy() will not append a NULL character at the end of the target string a1. This is where the vulnerability lies. Since the NULL character is used to mark the end of a string in C, the absence of this character can lead to a buffer overflow condition.