原文:https://medium.com/@_bl4de/hidden-directories-and-files-as-a-source-of-sensitive-information-about-web-application-84e5c534e5ad

Web服务器上“不小心”留下的隐藏目录和文件可能是非常有价值的敏感信息来源。在Web应用程序根文件夹下面,可能隐藏着大量的宝贵信息,例如源代码版本系统文件夹和文件(.git、.gitignore和.svn)、项目配置文件(.npmrc、Package.json以及.htaccess)、具有常见扩展名的自定义配置文件(如config.json、config.yml、config.xml等)。

一般而言,这些资源可分为如下所示的几种类型:

  • 源代码版本控制系统
  • IDE (集成开发环境)配置文件
  • 特定于项目和/或技术的配置和设置文件

下面,我们将对其展开深入的介绍——在哪里可以找到它们,以及可以预期得到什么样的信息。

源代码版本控制系统


Git


Git是“(…)一个免费的开源分布式版本控制系统,旨在以快速和高效的方式处理各种规模的项目”。GitHub.com是目前最流行的源代码版本控制系统之一,尤其是在开放源码领域。此外,许多公司也会使用自己的GitLab以及GitHub Enterprise或BitBucket

Git对象的基本信息


新创建的Git存储库包含一些默认的文件夹和文件,用于存储所有的相关信息。下图展示的是sample.git文件夹,其中已经完成了一次提交:

.git文件夹的基本结构

让我们从攻击者的角度来研究一番。Git存储库的内容都是以对象的形式来管理的,它们都被存储在.git/objects文件夹中。

对象可以是以下三种类型之一:commit、tree和blob。

其中,Commit类型的对象存放的是提交操作方面的信息,具有当前tree(文件夹和文件结构)对象的哈希值。

Tree类型的对象用来存放文件夹和文件结构的信息——每个文件夹或文件都有自己的对象哈希值,它们都存储在tree对象中。因此,它可能存放的是另一棵树(文件夹,在文件夹结构中的下一级中)或文件的信息。

Blob是保存文件内容的Git对象类型。换句话说,如果您知道特定文件的对象哈希值,则可以使用git cat-file命令来读取该文件的内容。

当您在Web服务器上找到.git文件夹时,可以通过一种简单的方法来获取任意文件的内容,这时,只需下载和读取git对象。有时候,如果你足够幸运,可以尝试使用标准git clone命令克隆存储库,或者只运行带有-r选项的wget命令,以递归方式下载所有.git文件夹。但由于某些原因(例如没有必需的凭证或缺少wget命令),这种方法并不总是可行的。下面,让我们假设所有这些选项都是不可能的。

为了确定.git文件夹可用,只需检查是否收到HTTP 403响应(或类似的响应,但不是404,因为这意味着该服务器或该位置没有.git文件夹):

403响应表明服务器上存在.git文件夹

使用本地Git存储库反射远程文件和文件夹


为此,我们必须创建自己的、具有框架文件夹结构的本地“dummy”.git存储库,并从远程服务器下载Git对象。

首先,创建dummy Git文件夹:

$ git init

该命令将使用所有必需的文件和文件夹初始化空的Git存储库。

检索和读取有关对象的信息


要开始从Git存储库中检索信息,首先必须找到出发点。Git将所有信息都保存在日志文件中,该文件位于.git/logs/head下面。

.git/logs/head示例文件中的内容

如果.git/logs/head无法正常使用,同时.git返回Forbidden 403的话,这意味着它就在该位置,请尝试.git/logs/HEAD。

下图展示的是这个文件的示例行:

0000000000000000000000000000000000000000 07603070376d63d911f608120eb4b5489b507692 
bloorq@gmail.com <bloorq@gmail.com> 1452195279 +0000    commit (initial): index.php initial commit

前两个字符串是(上次和当前提交的)对象哈希值——这正是我们要寻找的。因为这是第一次提交,所以,第一个哈希值只包含0(因为很明显,之前还没有提交过),第二个哈希值包含的是当前提交的相关信息。

首先,我们必须创建对象的有效路径。该路径包含到存储库中所有对象的公共路径,即.git/objects,后面还跟有通过哈希值构建的两部分,一个是目录名(哈希值的前两个字符),另一个是文件名(哈希值的其余部分,从第三个字符开始)。因此,要获得哈希值为07603070376D63D911F608120EB4B5489B507692的对象,应尝试打开以下URL:

localhost/testapp/.git/objects/07/603070376d63d911f608120eb4b5489b507692

这时,将弹出文件下载窗口:

请记住——您必须将该文件保存在之前创建的dummy Git文件夹中——这是能够读取Git对象内容的最简单方法,因为默认情况下,Git(程序)将在那里查找它们。因此,请确保将其保存在如下完全相同的位置:

path-to-your-dummy-git-repository/.git/objects/07/603070376d63d911f608120eb4b5489b507692

现在,git cat-file应该是您最得力的助手了。为了检查对象的类型,可以使用以下命令:

$ git cat-file -t <hash>

为了显示对象的内容,可以使用如下所示的命令:

$ git cat-file -p <hash>

现在,我们可以检查以前保存的对象的类型并读取其内容了(我是在本地主机上创建的存储库中执行的该操作,但是对于任何Git存储库,您在计算机上都会得到完全相同的结果——只有哈希值会有所不同)。

当您查看提交描述时,您可以找到有关实际tree对象哈希值的信息——正如我前面提到的,tree对象中存放了当前文件夹结构(更准确地说:完成提交时的文件夹结构)的信息。使用上面的方法,可以看到:

正如您所看到的,目前只有一个文件,即index.php,而且,我们还知道它的对象哈希值和类型,即blob。这就是我们需要使用与以前读取commit对象和tree对象的内容相同的方法来查看文件内容的原因(首先,您必须从Web服务器下载该对象,具体如上所述):

看到了吧!

需要注意的是,这是index.php的内容,正如对象07603070376D63D911F608120EB4B5489B507692所描述的提交信息那样。如果查看日志文件,可以看到有第二次提交(由对象哈希值4db7a14eee2cd3ff529278b75e1653e677fe1d02可以看出),最后一次提交时,它包含了所有最后的更改,难道index.php的内容与我们到目前为止看到的有所不同?

执行相关的步骤(读取提交内容以查找tree对象的哈希值,然后读取tree对象以查找index.php的哈希值等)后,我们将看到index.php的实际内容:

bl4de on Rafals-MacBook in /Library/WebServer/Documents/testapp $ git cat-file -p a4215057b6545240452087ad4d015bf9b5b817c5
<?php
echo "Hello testapp!";

$i = 100;
echo "Value of i is $i";

bl4de on Rafals-MacBook in /Library/WebServer/Documents/testapp $

因为手动对象检索的效率不高,而且非常耗时,所以,我用Python创建了一个简单的控制台工具来自动化这个过程,具体代码可以从这里下载。

.gitignore文件


如果我们发现Web服务器上的.git文件夹被删除了,那么,我们不妨看看.gitignore文件。这个文件的用途很单纯——保存不应该提交到存储库中的那些文件夹和文件的名称(但这并不意味着它们不存在——它们只是不作为Git存储库的一部分存在而已)。因此,这是找到所有无法以上述方式“透露”的内容的最简单的方法。

gitignore文件表明存在一些让人感兴趣的文件和文件夹。

Subversion (SVN)


Subversion(或SVN)是Apache Software Foundation创建的源代码版本控制系统,到目前为止,它仍然非常流行,并且具有广泛的应用,特别是在拥有大量遗留代码库的企业环境中。

SVN文件夹和文件的示例结构如下所示:

含有svn存储库的示例.svn文件夹

在我们看来,最重要的是SQLite数据库的wc.db文件以及pristine/目录中的内容。在wc.db中,我们能够找到用于pristine/目录中的文件名的哈希值,因此,我们必须从这个文件开始入手。

要从Subversion获取信息,我们首先需要确保wc.db文件可用。为此,我们可以尝试在Web浏览器中打开以下路径:

http://server/path_to_vulnerable_site/.svn/wc.db

如果出现弹出式的下载窗口,则意味着其余的.svn文件都有可能存在。首先,我们需要阅读wc.db的内容以获取有关文件的哈希值信息(这里没有.logs目录,就像上面描述的Git仓库一样)。

为了阅读wc.db的内容,我们可以借助于SQLite控制台客户端(或任何其他管理SQLite数据库的工具):

$ sqlite3 wc.db 
SQLite version 3.8.10.2 2015-05-20 18:17:19
Enter ".help" for usage hints.
sqlite> .databases
seq  name             file                                                      
---  ---------------  ----------------------------------------------------------
0    main             /Users/bl4de/hacking/playground/wc.db                     
sqlite> .dump
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE REPOSITORY (   id INTEGER PRIMARY KEY AUTOINCREMENT,   root  TEXT UNIQUE NOT NULL,   uuid  TEXT NOT NULL   );
INSERT INTO "REPOSITORY" VALUES(1,'svn+ssh://192.168.1.4/var/svn-repos/project_wombat','88dcec91-39c3-4b86-8627-702dd82cfa09');

(...)

INSERT INTO "NODES" VALUES(1,'trunk',0,'',1,'trunk',1,'normal',NULL,NULL,'dir',X'2829','infinity',NULL,NULL,1,1456055578790922,'bl4de',NULL,NULL,NULL,NULL);
INSERT INTO "NODES" VALUES(1,'',0,NULL,1,'',1,'normal',NULL,NULL,'dir',X'2829','infinity',NULL,NULL,1,1456055578790922,'bl4de',NULL,NULL,NULL,NULL);
INSERT INTO "NODES" VALUES(1,'trunk/test.txt',0,'trunk',1,'trunk/test.txt',2,'normal',NULL,NULL,'file',X'2829',NULL,'$sha1$945a60e68acc693fcb74abadb588aac1a9135f62',NULL,2,1456056344886288,'bl4de',38,1456056261000000,NULL,NULL);
INSERT INTO "NODES" VALUES(1,'trunk/test2.txt',0,'trunk',1,'trunk/test2.txt',3,'normal',NULL,NULL,'file',NULL,NULL,'$sha1$6f3fb98418f14f293f7ad55e2cc468ba692b23ce',NULL,3,1456056740296578,'bl4de',27,1456056696000000,NULL,NULL);

(...)

看到针对NODES表的INSERT操作了吗? 其中,每个操作都包含文件名和SHA1哈希值,它们对应于pristine/文件夹中的相关条目:

$ ls -lA pristine/94/
total 8
-rw-r--r--@ 1 bl4de  staff  38 Feb 21 12:05 945a60e68acc693fcb74abadb588aac1a9135f62.svn-base

要将NODES中的值映射为文件名,我们需要执行下列步骤:

  • 删除$sha1$前缀
  • 添加.svn-base后缀
  • 使用哈希值中的前两个符号作为pristine/目录中的文件夹名称(在本例中为94)
  • 创建完整路径,在本示例中为:
http://server/path_to_vulnerable_site/.svn/pristine/94/945a60e68acc693fcb74abadb588aac1a9135f62.svn-base

当我们尝试在浏览器中打开该路径时,应该能够直接在浏览器中下载文件或显示其内容:

如何从.svn存储库中读取特定文件内容

此外,REPOSITORIES表中的相关条目指向原始存储库路径,即:

svn+ssh://192.168.1.4/var/svn-repos/project_wombat

这里有很多信息。实际上,在Web服务器上保留.svn文件夹是一个巨大的错误,因为这样做非常危险,有可能导致Web应用程序源代码被完全泄露。

IDE项目文件


许多开发人员使用的IDE(集成开发环境)有一个共同点——它们会将项目的设置和大量附加信息保存在自己的文件中,并且会为每个项目单独创建相应的文件。如果该类文件夹还保留在Web服务器上,那么,这就是Web应用程序的另一个重要的信息来源。

下面,让我们先考察一下JetBrains公司的各种产品(https://www.jetbrains.com/)。

JetBrains公司的IDE产品:IntelliJ IDEA、WebStorm、PHPStorm以及RubyMine


使用JetBrains产品开发的每个项目都会创建自己的隐藏目录.idea/,该目录中保存有当前项目、其文件、目录和IDE设置的所有信息。

.idea JetBrains文件夹的示例内容

从安全研究人员的角度来看,其中一个文件非常有价值,它就是workspace.xml,其中包含许多有用的信息,例如用来枚举应用程序的所有文件和文件夹、源版本控制系统信息以及其他信息。

我们将逐一考察这些信息:

<?xml version="1.0" encoding="UTF-8"?>
    (...)
    <component name="FileEditorManager">
        <leaf>
          <file leaf-file-name="README.md" pinned="false" current-in-tab="false">
            <entry file="file://$PROJECT_DIR$/README.md">
                (...)
    </component>
(...)

组件名称为“FileEditorManager”的所有节点都包含全部的文件及其相对路径(相对于该项目的根目录)。简单来说,这些就是将要在主项目文件夹中执行的Bash命令ls的XML包装的产物:)

如果仔细查看每个组件节点,您将找到已使用的控件版本系统的信息,具体如下例所示:

<component name="Git.Settings">
    <option name="UPDATE_TYPE" value="MERGE" />
    <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
  </component>

此外,在组件名称为"TaskManager"的节点中,还有关于提交操作以及在项目文件上执行的其他任务的相关信息:

(...)
    <task id="LOCAL-00211" summary="change WebSocket port to 1099">
      <created>1436206418000</created>
      <option name="number" value="00211" />
      <option name="project" value="LOCAL" />
      <updated>1436206418000</updated>
    </task>
(...)

另一个让我们感兴趣的东东就是变更的历史记录,它们都存储在组件名称为“ChangeListManager”的节点中:

<component name="ChangeListManager">
        (...)
        <change type="DELETED" beforePath="$PROJECT_DIR$/chat/node_modules/socket.io/node_modules/socket.io-adapter/node_modules/debug/Makefile" afterPath="" />
        (...)
    </component>

同时,还存储在组件名称为“editorHistoryManager”节点中:

(...)
<entry file="file://$PROJECT_DIR$/public_html/vendor/angular/angular.js"> <provider editor-type-id="text-editor" selected="true"> <state vertical-scroll-proportion="0.0"> <caret column="29" line="3233" selection-end-column="29" selection-end-line="3233" selection-start-column="29" selection-start-line="3233"> </caret></state> </provider> </entry>
(...)

如果开发人员曾经使用集成数据库管理器来管理数据库,那么,还会有另一个非常有趣的文件,即dataSources.ids,我们可以在其中找到数据库结构、dataSource.xml、dataSources.xml、dataSources.local.xml和dbnavigator.xml包含的示例信息:

<database>
          <name value="database_name" />
          <description value="" />
          <database-type value="MYSQL" />
          <config-type value="BASIC" />
          <database-version value="5.7" />
          <driver-source value="BUILTIN" />
          <driver-library value="" />
          <driver value="" />
          <host value="localhost" />
          <port value="3306" />
          <database value="mywebapp" />
          <url-type value="DATABASE" />
          <os-authentication value="false" />
          <empty-password value="false" />
          <user value="root" />
          <password value="cm9vdA==" />   <!-- Base64 encoded -->
        </database>

甚至更多,比如dataSources.local.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="dataSourceStorageLocal">
    <data-source name="MySQL - mywebapp@localhost" uuid="8681098b-fc96-4258-8b4f-bfbd00012e2b">
      <secret-storage>master_key</secret-storage>
      <user-name>root</user-name>
      <schema-pattern>mywebapp.*</schema-pattern>
      <default-schemas>mywebapp.*</default-schemas>
    </data-source>
  </component>
</project>

当然,这一切都取决于项目本身,以及使用的IDE插件(如调试器、源版本控制或数据库管理器)。通常,到处搜索一下还是非常值得的,并且要仔细考察每个组件节点。

如您所见,这是非常有趣的信息来源。我建议大家下载任意一款JetBrains IDE产品(该公司的所有产品几乎都提供了为期30天的试用期——大家可以下载IntelliJ Idea社区版或PyCharm社区版并免费使用它们),然后创建示例项目,添加文件夹和文件,并尝试Git或SVN管理操作,创建示例数据库连接并使用数据库管理器——然后深入考察.idea/文件夹,看看能够从中找到哪些内容。

NetBeans IDE


NetBeans(https://netbeans.org/)是另一款非常流行的免费IDE,适用于Java、C/C++、PHP、HTML5和JavaScript等开发语言。目前,NetBeans隶属于Oracle公司,它已经成为Java应用程序的官方IDE,自然是免费和开源的。

NetBeans会在项目的根文件夹中创建自己的文件夹,用以存放所有的项目设置信息;具体来说,这个根文件目录为nbproject/。

虽然NetBeans并不像IntelliJ、PHPStorm或WebStorm那样“不利索”,但我们仍然可以找到一些有趣的信息,这些信息在针对易受攻击的Web应用程序中寻找特定的攻击向量时可能会有所裨益。实际上,project.xml就是一个着手考察NetBeans项目配置的一个不错的出发点。

示例.nbproject目录的内容

其他配置文件


NodeJS/JavaScript特有的配置文件


如果您曾经使用过JavaScript来构建现代项目的话,那么可能会惊讶于该类应用程序的根文件夹中含有的 .json和.rc 文件的数量之多。

实际上,这里有很多这样的配置文件,其中含有很多使用过的库的相关信息。虽然有些目录无法直接从浏览器中获得,甚至无法被用于文件夹和文件枚举的工具检测到,但是,仍然有几个目录是无处不在的,例如NPM配置文件(package.json、package-lock.json),其中含有全部应用程序的依赖项;以及linters JavaScript的配置文件,比如eslint或jshint或Bower的包管理器bower.json,等等。

下面,让我们来考察bower.json示例文件,其中存放有Bower的配置信息,以及Web应用程序(前端)中使用的包列表:

{
  "name": "testapp",
  "version": "2.1.0",
  "authors": [
    "Rafal 'bl4de' Janicki <email@gmail.com>"
  ],
  "description": "test application",
  "main": "index.html",
  "moduleType": [
    "globals"
  ],
  "license": "MIT",
  "dependencies": {
    "angular": "1.4",
    "pure": "~0.5.0",
    "angular-route": "~1.2.26",
    "angular-ui-router": "~0.2.11",
    "angular-bootstrap-datetimepicker": "latest",
    "angular-translate": "~2.6.1"
  },
  "devDependencies": {}
}

从安全角度来看,更有趣的是Node.js或io.js后端应用之类的程序,即package.json。由于它提供了服务器端详细的信息列表——使用的包,数据库连接器,中间件组件等,所以,该文件可能含有大量与潜在易受攻击软件相关的高价值信息。

如果您可以从服务器下载package.json,则可以通过简单的方法来识别应用程序使用的、可能存在漏洞的npm软件包,具体如下所示:

  • 确保安装了NodeJS,其中npm的版本为6或更高
  • 保存下载的package.json,并在保存目录中运行以下命令:
$ npm install
  • 完成上述操作后,您将获得类似如下所示的信息:
audited 9307 packages in 8.417s
found 9 vulnerabilities (4 low, 1 moderate, 4 high)
  run `npm audit fix` to fix them, or `npm audit` for details
  • 现在,运行audit命令(当然,您可能需要先在npmjs.org网站上注册一个帐户才能执行该操作):
$ npm audit
  • 执行上述命令时,您将获得一个报告,其中包含该工具所找到的所有安全漏洞:
$ npm audit

                       === npm audit security report ===                        

# Run  npm install gulp@4.0.0  to resolve 5 vulnerabilities
SEMVER WARNING: Recommended action is a potentially breaking change
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ High          │ Regular Expression Denial of Service                         │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ minimatch                                                    │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ gulp                                                         │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ gulp > vinyl-fs > glob-stream > glob > minimatch             │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://nodesecurity.io/advisories/118                       │
└───────────────┴──────────────────────────────────────────────────────────────┘

(...more dtails about every vulnerability...)


found 9 vulnerabilities (4 low, 1 moderate, 4 high) in 9307 scanned packages
  run `npm audit fix` to fix 1 of them.
  6 vulnerabilities require semver-major dependency updates.
  2 vulnerabilities require manual review. See the full report for details.

实际上,我们最好将该输出保存到单独的文件中,因为有时会在几个NPM模块中发现多达数百个已识别的弱点。最重要的是不要陷入“兔子洞”——其中一些安全问题更多的是理论上的漏洞,而不是可利用的漏洞,有些模块甚至可能根本就没有被项目所使用。

下面是一个示例包。JSON内容表明这里可能使用了MySQL数据库,并且通过WebSockets进行了一些客户机-服务器通信:

{
  "name": "Test application server dependencies",
  "version": "1.0.0",
  "author": "bl4de",
  "dependencies": {
    "socket.io": "^1.3.5",
    "mysql": "^2.9.0"
  }
}

这种信息能够助我们快速意识到,尝试常见的NoSQL注入可能不是最好的主意,因为应用程序使用的是标准的关系SQL数据库,也许,我们应该尝试检查应用程序是否容易受到SQL注入攻击。

此外,还有诸如.bowerrc、.eslintrc以及.jshintrc等文件。即使它们不包含非常敏感的信息,也总是有可能从中找到有关Web应用程序体系结构、使用的库和/或框架方面的一些细节信息,甚至是注释中的一些有价值的信息。如果我们在侦察阶段发现了它们,还是值得研究一番的。

GitLab CI/CD .gitlab-ci.yml配置文件


当项目使用GitLab Continous Integration (GitLab CI/CD)时,项目根文件夹中会含有一个非常特殊且脆弱的文件:.gitlab-ci.yml。该文件可能包含大量非常敏感的信息:有关测试和构建过程的详细信息,其中含有在此过程的每个步骤中执行的命令的详细信息,以及其他方面的关键信息。

您可以在此处找到.gitlab-ci.yml文件的示例

Ruby on Rails database.yml文件


如果您足够幸运的货,则会发现这个文件是可读的,那么,对于RoR(Ruby on Rails)应用程序来说,通常则意味着“game over”。它是主数据库的配置文件,其中包含连接到数据库所需的一切信息:用户名、密码以及其他配置方面的详细信息。

macOS .DS_Store文件


对于macOS系统来说,它的一个特殊之处在于,有一个名为.DS_Store的文件。该文件是由macOS文件资源管理器Finder(但不仅限于它)创建的,并且经常被错误地提交到源版本控制存储库中。

使.DS_Store文件非常有用的原因在于,它们通常保存有关Finder窗口配置的信息,包括代表文件和文件夹的图标的布局信息,这些图标通常在特定Finder窗口中显示,也就是说,我们获得了该窗口中的文件和文件夹的名称。如果您在Web服务器上找到了.DS_Store文件,则有可能在其中显示这些信息,并允许您通过“枚举”方式获得无法以任何其他方式找到的资源。当然,我们需要确保所使用的Dirb、Dirbuster、wfuzz或任何其他工具的字典中包含.DS_Store这一条目。

示例.DS_Store文件。我们不仅可以找到config、LICENSE、loader或package.json文件,也可以找到node_modules/、pages/、utils/和wrappers/文件夹——NodeJS应用程序的典型结构。

.DS_Store文件的主要问题在于,它们使用了Apple特定格式,并且难以阅读,尽管可以在网上找到解析内容的一些相关工具。描述这种格式的最佳资源之一(以及用于解析的Python库)是由Sebastian Neef撰写的Parsing the .DS_Store file format文章。

实际上,我们可以借助自己喜爱的工具,通过现成的字典来探索隐藏的文件夹和文件:)

最常用的查找隐藏文件夹和文件的方法之一是使用枚举工具(例如Dirbuster、Dirb以及本人最喜欢的WFuzz,当然,这只是其中的几个例子而已),其中的字典包含数十万个最流行的文件夹和文件名、robots.txt公共条目等。不久前,我根据其他几个在线字典自己打造了一个这样的字典(例如:http://github.com/danielmessler/seclists,或:https://github.com/danielmessler/robotsdisallow,这里要特别感谢Daniel Miessler为我们维护这两个优秀的存储库),同时,我自己还为其添加了几个条目。

目前,这本字典包含大约80K个条目,我发现用它来对付典型的公共Web服务器时非常有效。如果您希望亲自尝试一下的话,可以自行下载该字典,并与您喜欢的工具结合使用。

需要提醒的是,永远不要忘了检查Web服务器上是否存在本文介绍的文件夹中描述的任何内容。公开的Git或SVN存储库简直就是一个灾难,因为它允许下载Web应用程序的源代码,以及IntelliJ IDE项目配置文件夹。有时候,我们只需要找到这类资源,就能搞定整个Web应用程序(以及Web服务器本身)。

如果您有任何问题或反馈,请随时通过Twitter与我联系。

最后,祝阅读愉快!:)

点击收藏 | 2 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖