整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          Tomcat文件上傳面繞waf(流量層)

          Tomcat文件上傳面繞waf(流量層)

          遵守法律法規(guī),文章旨在提高安全軟件的應(yīng)變策略,嚴禁非法使用。

          寫在前面

          無意中看到ch1ng的文章覺得很有趣,不得不感嘆太厲害了,但我一看那長篇的函數(shù)總覺得會有更騷的東西,所幸還真的有,借此機會就發(fā)出來一探究竟,同時也不得不感慨下RFC文檔的妙處,當然本文針對的技術(shù)也僅僅只是在流量層面上waf的繞過。


          Pre

          很神奇對吧,當然這不是終點,接下來我們就來一探究竟。


          前置

          這里簡單說一下師傅的思路

          部署與處理上傳war的servlet是 org.apache.catalina.manager.HTMLManagerServlet

          在文件上傳時最終會通過處理 org.apache.catalina.manager.HTMLManagerServlet#upload


          調(diào)用的是其子類實現(xiàn)類org.apache.catalina.core.ApplicationPart#getSubmittedFileName

          這里獲取filename的時候的處理很有趣


          看到這段注釋,發(fā)現(xiàn)在RFC 6266文檔當中也提出這點

           Avoid including the "\" character in the quoted-string form of the filename parameter, as escaping is not implemented by some user agents, and "\" can be considered an illegal path character.



          那么我們的tomcat是如何處理的嘞?這里它通過函數(shù)HttpParser.unquote去進行處理。

          public static String unquote(String input) {
                  if (input==null || input.length() < 2) {
                      return input;
                  }
          
          
                  int start;
                  int end;
          
          
                  // Skip surrounding quotes if there are any
                  if (input.charAt(0)=='"') {
                      start=1;
                      end=input.length() - 1;
                  } else {
                      start=0;
                      end=input.length();
                  }
          
          
                  StringBuilder result=new StringBuilder();
                  for (int i=start ; i < end; i++) {
                      char c=input.charAt(i);
                      if (input.charAt(i)=='\\') {
                          i++;
                          result.append(input.charAt(i));
                      } else {
                          result.append(c);
                      }
                  }
                  return result.toString();
              }

          簡單做個總結(jié)。

          如果首位是"(前提條件是里面有\字符),那么就會去掉跳過從第二個字符開始,并且末尾也會往前移動一位,同時會忽略字符\,師傅只提到了類似test.\war這樣的例子。

          但其實根據(jù)這個我們還可以進一步構(gòu)造一些看著比較惡心的比如 filename=""y.\w\arK" 。

          s


          深入

          還是在 org.apache.catalina.core.ApplicationPart#getSubmittedFileName當中,一看到這個將字符串轉(zhuǎn)換成map的操作總覺得里面會有更騷的東西(這里先是解析傳入的參數(shù)再獲取,如果解析過程有利用點那么也會影響到后面參數(shù)獲取),不扯遠繼續(xù)回到正題


          首先它會獲取header參數(shù)Content-Disposition當中的值,如果以form-data或者attachment開頭就會進行我們的解析操作,跟進去一看果不其然,看到RFC2231Utility瞬間不困了


          后面這一坨就不必多說了,相信大家已經(jīng)很熟悉啦支持QP編碼,忘了的可以考古看看我之前寫的文章Java文件上傳大殺器-繞waf(針對commons-fileupload組件),這里就不再重復(fù)這個啦,我們重點看三元運算符前面的這段

          既然如此,我們先來看看這個hasEncodedValue判斷標準是什么,字符串末尾是否帶*

          1   public static boolean hasEncodedValue(final String paramName) {2     if (paramName !=null) {3       return paramName.lastIndexOf('*')==(paramName.length() - 1);4     }5     return false;6   }

          在看解密函數(shù)之前我們可以先看看RFC 2231文檔當中對此的描述,英文倒是很簡單不懂的可以在線翻一下,這里就不貼中文了


          Asterisks ("*") are reused to provide the indicator that language and character set 2   information is present and encoding is being used. A single quote ("'") is used to delimit the character set and language information at the beginning of the parameter value. Percent signs ("%") are used as the encoding flag, which agrees with RFC 2047.Specifically, an asterisk at the end of a parameter name acts as an indicator that character set and language information may appear at  the beginning of the parameter value. A single quote is used to separate the character set, language, and actual value information in the parameter value string, and an percent sign is used to flag octets encoded in hexadecimal.  For example:Content-Type: application/x-stuff;         title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A

          接下來回到正題,我們繼續(xù)看看這個解碼做了些什么


          public static String decodeText(final String encodedText) throws UnsupportedEncodingException {  final int langDelimitStart=encodedText.indexOf('\'');  if (langDelimitStart==-1) {    // missing charset    return encodedText;  }  final String mimeCharset=encodedText.substring(0, langDelimitStart);  final int langDelimitEnd=encodedText.indexOf('\'', langDelimitStart + 1);  if (langDelimitEnd==-1) {    // missing language    return encodedText;  }  final byte[] bytes=fromHex(encodedText.substring(langDelimitEnd + 1));  return new String(bytes, getJavaCharset(mimeCharset));}

          結(jié)合注釋可以看到標準格式@param encodedText - Text to be decoded has a format of {@code <charset>'<language>'<encoded_value>},分別是編碼,語言和待解碼的字符串,同時這里還適配了對url編碼的解碼,也就是fromHex函數(shù),具體代碼如下,其實就是url解碼

          private static byte[] fromHex(final String text) {  final int shift=4;  final ByteArrayOutputStream out=new ByteArrayOutputStream(text.length());  for (int i=0; i < text.length();) {    final char c=text.charAt(i++);    if (c=='%') {      if (i > text.length() - 2) {        break; // unterminated sequence      }      final byte b1=HEX_DECODE[text.charAt(i++) & MASK];      final byte b2=HEX_DECODE[text.charAt(i++) & MASK];      out.write((b1 << shift) | b2);    } else {      out.write((byte) c);    }  }  return out.toByteArray();}

          因此我們將值當中值得注意的點梳理一下

          1. 支持編碼的解碼
          2. 值當中可以進行url編碼
          3. @code<charset>'<language>'<encoded_value> 中間這位language可以隨便寫,代碼里沒有用到這個的處理


          既然如此那么我們首先就可以排出掉utf-8,畢竟這個解碼后就直接是明文,從Java標準庫當中的charsets.jar可以看出,支持的編碼有很多


          同時通過簡單的代碼也可以輸出

          Locale locale=Locale.getDefault();
          Map<String, Charset> maps=Charset.availableCharsets();
          StringBuilder sb=new StringBuilder();
          sb.append("{");
          for (Map.Entry<String, Charset> entry : maps.entrySet()) {
            String key=entry.getKey();
            Charset value=entry.getValue();
            sb.append("\"" + key + "\",");
          }
          sb.deleteCharAt(sb.length() - 1);
          sb.append("}");
          System.out.println(sb.toString());

          運行輸出

          //res{"Big5","Big5-HKSCS","CESU-8","EUC-JP","EUC-KR","GB18030","GB2312","GBK","IBM-Thai","IBM00858","IBM01140","IBM01141","IBM01142","IBM01143","IBM01144","IBM01145","IBM01146","IBM01147","IBM01148","IBM01149","IBM037","IBM1026","IBM1047","IBM273","IBM277","IBM278","IBM280","IBM284","IBM285","IBM290","IBM297","IBM420","IBM424","IBM437","IBM500","IBM775","IBM850","IBM852","IBM855","IBM857","IBM860","IBM861","IBM862","IBM863","IBM864","IBM865","IBM866","IBM868","IBM869","IBM870","IBM871","IBM918","ISO-2022-CN","ISO-2022-JP","ISO-2022-JP-2","ISO-2022-KR","ISO-8859-1","ISO-8859-13","ISO-8859-15","ISO-8859-2","ISO-8859-3","ISO-8859-4","ISO-8859-5","ISO-8859-6","ISO-8859-7","ISO-8859-8","ISO-8859-9","JIS_X0201","JIS_X0212-1990","KOI8-R","KOI8-U","Shift_JIS","TIS-620","US-ASCII","UTF-16","UTF-16BE","UTF-16LE","UTF-32","UTF-32BE","UTF-32LE","UTF-8","windows-1250","windows-1251","windows-1252","windows-1253","windows-1254","windows-1255","windows-1256","windows-1257","windows-1258","windows-31j","x-Big5-HKSCS-2001","x-Big5-Solaris","x-COMPOUND_TEXT","x-euc-jp-linux","x-EUC-TW","x-eucJP-Open","x-IBM1006","x-IBM1025","x-IBM1046","x-IBM1097","x-IBM1098","x-IBM1112","x-IBM1122","x-IBM1123","x-IBM1124","x-IBM1166","x-IBM1364","x-IBM1381","x-IBM1383","x-IBM300","x-IBM33722","x-IBM737","x-IBM833","x-IBM834","x-IBM856","x-IBM874","x-IBM875","x-IBM921","x-IBM922","x-IBM930","x-IBM933","x-IBM935","x-IBM937","x-IBM939","x-IBM942","x-IBM942C","x-IBM943","x-IBM943C","x-IBM948","x-IBM949","x-IBM949C","x-IBM950","x-IBM964","x-IBM970","x-ISCII91","x-ISO-2022-CN-CNS","x-ISO-2022-CN-GB","x-iso-8859-11","x-JIS0208","x-JISAutoDetect","x-Johab","x-MacArabic","x-MacCentralEurope","x-MacCroatian","x-MacCyrillic","x-MacDingbat","x-MacGreek","x-MacHebrew","x-MacIceland","x-MacRoman","x-MacRomania","x-MacSymbol","x-MacThai","x-MacTurkish","x-MacUkraine","x-MS932_0213","x-MS950-HKSCS","x-MS950-HKSCS-XP","x-mswin-936","x-PCK","x-SJIS_0213","x-UTF-16LE-BOM","X-UTF-32BE-BOM","X-UTF-32LE-BOM","x-windows-50220","x-windows-50221","x-windows-874","x-windows-949","x-windows-950","x-windows-iso2022jp"}

          這里作為掩飾我就隨便選一個了UTF-16BE


          同樣的我們也可以進行套娃結(jié)合上面的filename=""y.\w\arK"改成filename="UTF-16BE'Y4tacker'%00%22%00y%00%5C%004%00.%00%5C%00w%00%5C%00a%00r%00K"

          接下來處理點小加強,可以看到在這里分隔符無限加,而且加了號的字符之后也會去除一個號


          因此我們最終可以得到如下payload,此時僅僅基于正則的waf規(guī)則就很有可能會失效

          ------WebKitFormBoundaryQKTY1MomsixvN8vXContent-Disposition: form-data*;;;;;;;;;;name*="UTF-16BE'Y4tacker'%00d%00e%00p%00l%00o%00y%00W%00a%00r";;;;;;;;filename*="UTF-16BE'Y4tacker'%00%22%00y%00%5C%004%00.%00%5C%00w%00%5C%00a%00r%00K"Content-Type: application/octet-stream123------WebKitFormBoundaryQKTY1MomsixvN8vX--

          可以看見成功上傳

          變形 更新2022-06-20

          這里測試版本是Tomcat8.5.72,這里也不想再測其他版本差異了只是提供一種思路

          在此基礎(chǔ)上我發(fā)現(xiàn)還可以做一些新的東西,其實就是對org.apache.tomcat.util.http.fileupload.ParameterParser#parse(char[], int, int, char)函數(shù)進行深入分析

          在獲取值的時候paramValue=parseQuotedToken(new char[] {separator });,其實是按照分隔符;分割,因此我們不難想到前面的東西其實可以不用"進行包裹,在parseQuotedToken最后返回調(diào)用的是return getToken(true);,這個函數(shù)也很簡單就不必多解釋

          private String getToken(final boolean quoted) {        // Trim leading white spaces        while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) {            i1++;        }        // Trim trailing white spaces        while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) {            i2--;        }        // Strip away quotation marks if necessary        if (quoted            && ((i2 - i1) >=2)            && (chars[i1]=='"')            && (chars[i2 - 1]=='"')) {            i1++;            i2--;        }        String result=null;        if (i2 > i1) {            result=new String(chars, i1, i2 - i1);        }        return result;    }

          可以看到這里也是成功識別的

          既然調(diào)用parse解析參數(shù)時可以不被包裹,結(jié)合getToken函數(shù)我們可以知道在最后一個參數(shù)其實就不必要加;了,并且解析完通過params.get("filename")獲取到參數(shù)后還會調(diào)用到org.apache.tomcat.util.http.parser.HttpParser#unquote那也可以基于此再次變形

          為了直觀這里就直接明文了,是不是也很神奇

          擴大利用面

          現(xiàn)在只是war包的場景,多多少少影響性被降低,但我們這串代碼其實抽象出來就一個關(guān)鍵


          Part warPart=request.getPart("deployWar");
          String filename=warPart.getSubmittedFileName();

          通過查詢官方文檔,可以發(fā)現(xiàn)從Servlet3.1開始,tomcat新增了對此的支持,也就意味著簡單通過javax.servlet.http.HttpServletRequest#getParts即可,簡化了我們文件上傳的代碼負擔(dān)(如果我是開發(fā)人員,我肯定首選也會使用,誰不想當懶狗呢)

          getSubmittedFileName
          String getSubmittedFileName()
          Gets the file name specified by the client
          Returns:
          the submitted file name
          Since:
          Servlet 3.1

          更新Spring 2022-06-20

          早上起床想著昨晚和陳師的碰撞,起床后又看了下陳師的星球,看到這個不妨再試試Spring是否也按照了RFC的實現(xiàn)呢(畢竟Spring內(nèi)置了Tomcat,可能會有類似的呢)


          Spring為我們提供了處理文件上傳MultipartFile的接口

          public interface MultipartFile extends InputStreamSource {    String getName(); //獲取參數(shù)名    @Nullable    String getOriginalFilename();//原始的文件名    @Nullable    String getContentType();//內(nèi)容類型    boolean isEmpty();    long getSize(); //大小    byte[] getBytes() throws IOException;// 獲取字節(jié)數(shù)組    InputStream getInputStream() throws IOException;//以流方式進行讀取    default Resource getResource() {        return new MultipartFileResource(this);    }    // 將上傳的文件寫入文件系統(tǒng)    void transferTo(File var1) throws IOException, IllegalStateException;  // 寫入指定path    default void transferTo(Path dest) throws IOException, IllegalStateException {        FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest));    }}

          而spring處理文件上傳邏輯的具體關(guān)鍵邏輯在org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#parseRequest,抄個文件上傳demo來進行測試分析

          Spring4

          這里我測試了springboot1.5.20.RELEASE內(nèi)置Spring4.3.23,具體小版本之間是否有差異這里就不再探究

          其中關(guān)于org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#parseRequest的調(diào)用也有些不同

          private void parseRequest(HttpServletRequest request) {    try {        Collection<Part> parts=request.getParts();        this.multipartParameterNames=new LinkedHashSet(parts.size());        MultiValueMap<String, MultipartFile> files=new LinkedMultiValueMap(parts.size());        Iterator var4=parts.iterator();        while(var4.hasNext()) {            Part part=(Part)var4.next();            String disposition=part.getHeader("content-disposition");            String filename=this.extractFilename(disposition);            if (filename==null) {                filename=this.extractFilenameWithCharset(disposition);            }            if (filename !=null) {                files.add(part.getName(), new StandardMultipartHttpServletRequest.StandardMultipartFile(part, filename));            } else {                this.multipartParameterNames.add(part.getName());            }        }        this.setMultipartFiles(files);    } catch (Throwable var8) {        throw new MultipartException("Could not parse multipart servlet request", var8);    }}

          簡單看了下和tomcat之前的分析很像,這里Spring4當中同時也是支持filename*格式的


          看看具體邏輯

          private String extractFilename(String contentDisposition, String key) {        if (contentDisposition==null) {            return null;        } else {            int startIndex=contentDisposition.indexOf(key);            if (startIndex==-1) {                return null;            } else {                //截取filename=后面的內(nèi)容                String filename=contentDisposition.substring(startIndex + key.length());                int endIndex;                //如果后面開頭是“則截取”“之間的內(nèi)容                if (filename.startsWith("\"")) {                    endIndex=filename.indexOf("\"", 1);                    if (endIndex !=-1) {                        return filename.substring(1, endIndex);                    }                } else {                  //可以看到如果沒有“”包裹其實也可以,這和當時陳師分享的其中一個trick是符合的                    endIndex=filename.indexOf(";");                    if (endIndex !=-1) {                        return filename.substring(0, endIndex);                    }                }                return filename;            }        }    }

          簡單測試一波,與心中結(jié)果一致

          同時由于indexof默認取第一位,因此我們還可以加一些干擾字符嘗試突破waf邏輯


          如果filename*開頭但是spring4當中沒有關(guān)于url解碼的部分


          沒有這部分會出現(xiàn)什么呢?我們只能自己發(fā)包前解碼,這樣的話如果出現(xiàn)00字節(jié)就會報錯,報錯后

          看起來是spring框架解析header的原因,但是這里報錯信息也很有趣將項目地址的絕對路徑拋出了,感覺不失為信息收集的一種方式

          Spring5

          也是隨便來個新的springboot2.6.4的,來看看spring5的,小版本間差異不測了,經(jīng)過測試發(fā)現(xiàn)spring5和spring4之間也是有版本差異處理也有些不同,同樣是在parseRequest

          private void parseRequest(HttpServletRequest request) {        try {            Collection<Part> parts=request.getParts();            this.multipartParameterNames=new LinkedHashSet(parts.size());            MultiValueMap<String, MultipartFile> files=new LinkedMultiValueMap(parts.size());            Iterator var4=parts.iterator();            while(var4.hasNext()) {                Part part=(Part)var4.next();                String headerValue=part.getHeader("Content-Disposition");                ContentDisposition disposition=ContentDisposition.parse(headerValue);                String filename=disposition.getFilename();                if (filename !=null) {                    if (filename.startsWith("=?") && filename.endsWith("?=")) {                        filename=StandardMultipartHttpServletRequest.MimeDelegate.decode(filename);                    }                    files.add(part.getName(), new StandardMultipartHttpServletRequest.StandardMultipartFile(part, filename));                } else {                    this.multipartParameterNames.add(part.getName());                }            }            this.setMultipartFiles(files);        } catch (Throwable var9) {            this.handleParseFailure(var9);        }    }

          很明顯可以看到這一行filename.startsWith("=?") && filename.endsWith("?="),可以看出Spring對文件名也是支持QP編碼

          在上面能看到還調(diào)用了一個解析的方法org.springframework.http.ContentDisposition#parse

          ,多半就是這里了,那么繼續(xù)深入下

          可以看到一方面是QP編碼,另一方面也是支持filename*,同樣獲取值是截取"之間的或者沒找到就直接截取=后面的部分


          如果是filename*后面的處理邏輯就是else分之,可以看出和我們上面分析spring4還是有點區(qū)別就是這里只支持UTF-8/ISO-8859-1/US_ASCII,編碼受限制

          int idx1=value.indexOf(39);int idx2=value.indexOf(39, idx1 + 1);if (idx1 !=-1 && idx2 !=-1) {  charset=Charset.forName(value.substring(0, idx1).trim());  Assert.isTrue(StandardCharsets.UTF_8.equals(charset) || StandardCharsets.ISO_8859_1.equals(charset), "Charset should be UTF-8 or ISO-8859-1");  filename=decodeFilename(value.substring(idx2 + 1), charset);} else {  filename=decodeFilename(value, StandardCharsets.US_ASCII);}

          但其實仔細想這個結(jié)果是符合RFC文檔要求的


          接著我們繼續(xù)后面會繼續(xù)執(zhí)行decodeFilename


          代碼邏輯很清晰字符串的解碼,如果字符串是否在RFC 5987文檔規(guī)定的Header字符就直接調(diào)用baos.write寫入

          attr-char=ALPHA / DIGIT
                            / "!" / "#" / "$" / "&" / "+" / "-" / "."
                            / "^" / "_" / "`" / "|" / "~"
                            ; token except ( "*" / "'" / "%" )

          如果不在要求這一位必須是%然后16進制解碼后兩位,其實就是url解碼,簡單測試即可


          參考文章

          習(xí)總目標

          本次學(xué)習(xí)目標

          第一章 HTML&CSS

          1. Web基本概念

          1.1 服務(wù)器和客戶端的概念

          1.1.1 客戶端的作用

          與用戶進行交互,用于接收用戶的輸入(操作)、展示服務(wù)器端的數(shù)據(jù)以及向服務(wù)器傳遞數(shù)據(jù)

          1.1.2 常見的客戶端

          • PC端網(wǎng)頁
          • 移動端
          • Iot設(shè)備

          1.1.3 服務(wù)器的作用

          與客戶端進行交互,接收客戶端的數(shù)據(jù)、處理具體的業(yè)務(wù)邏輯、傳遞給客戶端其需要的數(shù)據(jù)

          1.1.4 服務(wù)器的概念

          “服務(wù)器”是一個非常寬泛的概念,從硬件而言:服務(wù)器是計算機的一種,它比普通計算機運行更快、負載更高、價格更貴。服務(wù)器在網(wǎng)絡(luò)中為其它客戶機(如PC機、智能手機、ATM等終端甚至是火車系統(tǒng)等大型設(shè)備)提供計算或者應(yīng)用服務(wù)。從軟件而言:服務(wù)器其實就是安裝在計算機上的一個軟件,根據(jù)其作用的不同又可以分為各種不同的服務(wù)器,例如應(yīng)用服務(wù)器、數(shù)據(jù)庫服務(wù)器、Redis服務(wù)器、DNS服務(wù)器、ftp服務(wù)器等等

          綜上所述:用我們自己的話來總結(jié)的話,服務(wù)器其實就是一臺(或者一個集群)安裝了服務(wù)器軟件的高性能計算機

          1.1.5 常見的服務(wù)器硬件設(shè)備

          • 刀片服務(wù)器
          • 塔式服務(wù)器
          • 機房

          1.1.6 常見的服務(wù)器操作系統(tǒng)

          服務(wù)器是一臺計算機,它必須安裝操作系統(tǒng)之后才能夠安裝使用服務(wù)器軟件

          • Linux系統(tǒng): 使用最多的服務(wù)器系統(tǒng),安全穩(wěn)定、性能強勁、開源免費(或少許費用)。
          • Unix系統(tǒng): 和硬件服務(wù)器捆綁銷售,版權(quán)不公開,用法和Linux一樣。
          • Windows Server系統(tǒng): 源代碼不開放,費用高昂,漏洞較多,性能較差,運維成本高。

          1.1.7 常見的服務(wù)器軟件

          硬件服務(wù)器裝好系統(tǒng),就可以安裝應(yīng)用軟件了,像我們熟知的Tomcat、MySQL、Redis、FastDFS、ElasticSearch等等都是服務(wù)器應(yīng)用軟件。它們分別提供自己特定的服務(wù)器功能。如果一臺服務(wù)器上安裝了Tomcat,我們會就會把這臺服務(wù)器叫做Tomcat服務(wù)器;如果裝了MySQL,就叫做MySQL服務(wù)器。很顯然,開發(fā)過程中需要很多這樣的服務(wù)器。

          1.1.8 虛擬機服務(wù)器

          • VMWare虛擬機

          通常來說VMWare用于開發(fā)人員在本地電腦上搭建一個模擬的服務(wù)器環(huán)境,或自己裝一些東西測試,不是團隊共同使用的正式環(huán)境。

          • 彈性云服務(wù)器

          使用彈性云服務(wù)器最大的好處就是彈性伸縮。什么是彈性伸縮呢?我現(xiàn)在的服務(wù)器是20G內(nèi)存,因為訪問量暴漲我需要把內(nèi)存擴容到80G,要是物理的硬件服務(wù)器就需要買來新的內(nèi)存條插入主板上的內(nèi)存插槽。而彈性云服務(wù)器只需要改一下內(nèi)存容量的參數(shù)就行了,非常方便。等訪問量下降了,再把內(nèi)存容量調(diào)回來就可以,不僅方便,而且可以精準的在訪問高峰期提高服務(wù)器配置而不是一直維持高配,節(jié)約成本。

          1.2 服務(wù)器端應(yīng)用程序

          服務(wù)器端應(yīng)用程序就是運行在應(yīng)用服務(wù)器軟件上,用于處理具體業(yè)務(wù)功能的一個應(yīng)用程序,而我們學(xué)習(xí)JavaEE開發(fā)的目的就是編寫服務(wù)器端應(yīng)用程序。例如: 淘寶、滴滴、京東等等項目都是服務(wù)器端應(yīng)用程序

          1.3 業(yè)務(wù)

          業(yè)務(wù)就是服務(wù)器應(yīng)用程序中的各個功能,例如商城里面的: 注冊、登錄、添加購物車、提交訂單、結(jié)算訂單等等都稱之為業(yè)務(wù)

          1.4 請求和響應(yīng)

          1.4.1 請求

          請求是從客戶端發(fā)送給服務(wù)器,主要用于將客戶端的數(shù)據(jù)傳遞給服務(wù)器

          1.4.2 響應(yīng)

          響應(yīng)是從服務(wù)器發(fā)送給客戶端,主要用于將服務(wù)器的數(shù)據(jù)傳遞給客戶端

          2. 本階段技術(shù)體系

          2.1 本階段技術(shù)體系

          2.1.1 客戶端技術(shù)

          HTML、CSS、JavaScript、Vue、Ajax、Axios

          2.1.2 服務(wù)器端技術(shù)

          Tomcat、Servlet、Request、Response、Cookie、Session、Filter、Listener、Thymeleaf

          2.1.3 持久層技術(shù)(數(shù)據(jù)庫技術(shù)已學(xué))

          MySql、JDBC、連接池、DBUtils

          2.1.4 圖解

          明:本欄目所使用的素材都是凱哥學(xué)堂VIP學(xué)員所寫,學(xué)員有權(quán)匿名,對文章有最終解釋權(quán);凱哥學(xué)堂旨在促進VIP學(xué)員互相學(xué)習(xí)的基礎(chǔ)上公開筆記。

          Html5-網(wǎng)頁編程

          跑馬燈:<marquee>標簽 direction屬性:移動方向 loop屬性:循環(huán)次數(shù) behavior屬性:出發(fā)點 scrollamount屬性:移動速度

          標記 針對網(wǎng)頁進行一個分區(qū)

          樣式其實就是標簽中的style的屬性內(nèi)容,頁面的美化布局都是由這些屬性來控制的,在標記中找到的屬性是有限的。如果都是在style屬性中去寫樣式內(nèi)容,那么每個都得去寫,勢必就會有許多的代碼的重復(fù),所以這個時候就引入了css樣式表,去統(tǒng)一的、綜合的管理這些style屬性里的內(nèi)容。

          樣式表就是標記屬性配置。

          內(nèi)部樣式表:head標記之間寫一個 style 標記即可

          外部樣式表:head標記之間寫一個 link 標記引入外部樣式文件

          樣式選擇器:

          如何給樣式表起名字,能概括那些標記。

          標記樣式:直接寫標記名稱可以設(shè)置整個頁面此標記的樣式。只要網(wǎng)頁中出現(xiàn)這個標記,那么就全部映對這個樣式。不太推薦

          標記名稱:

          ID名稱設(shè)置

          注意:標記名稱樣式大于id名稱樣式,id名稱樣式會覆蓋標記名稱樣式

          . class屬性樣式,這種樣式需要你在標記中寫 class屬性才可以引入

          id 和 class 的區(qū)別:

          id 是標記的起個名字,這個名字是全局唯一

          class 引入樣式表 可以重復(fù)引入 也可以多引入


          主站蜘蛛池模板: 中文字幕VA一区二区三区| 精品免费国产一区二区三区 | 精品国产一区二区三区AV性色| 天堂成人一区二区三区| 日韩精品午夜视频一区二区三区| 日本精品一区二区三本中文| 精品中文字幕一区二区三区四区| 国产福利电影一区二区三区久久老子无码午夜伦不 | 免费看无码自慰一区二区| 日本丰满少妇一区二区三区| 精品国产一区二区三区久久蜜臀| 国产午夜精品免费一区二区三区 | 国产亚洲福利一区二区免费看| 亚洲a∨无码一区二区| 久久久久一区二区三区| 久久精品综合一区二区三区| 国产一区高清视频| 中文字幕日本精品一区二区三区| 午夜DV内射一区区| 一区二区三区电影网| 色噜噜一区二区三区| 中文人妻av高清一区二区| 伊人精品视频一区二区三区| 2020天堂中文字幕一区在线观| 国产精品无码一区二区三级| 国产一在线精品一区在线观看| 亚洲Av无码一区二区二三区| 乱色熟女综合一区二区三区| 精品人无码一区二区三区 | 国产日韩一区二区三区| 亚洲日本一区二区三区在线不卡| 一区二区中文字幕| 无码日韩人妻AV一区二区三区| 亚洲一区中文字幕在线电影网| 夜色阁亚洲一区二区三区| 国产精品夜色一区二区三区| 2014AV天堂无码一区| 国产精品盗摄一区二区在线| 麻豆天美国产一区在线播放| 国产一区二区三区福利| 国精产品一区一区三区有限公司|