[패치됨] Laravel 7.x – XSS vulnerability

이 포스팅은 laravel 7.0 ~ 7.1.2 까지 버전에 존재하는 취약점 입니다.
사용중이신 버전이 포함되어있다면 laravel 7.1.3이상으로 업데이트 하시기 바랍니다.

Laravel news of XSS vector

laravel-news 에서 7.1.2 미만의 버전에서 XSS 공격 포인트가 있으니 업데이트하라는 소식이 들려와 어느부분이 취약한지 궁금해 해당 부분에 대한 코드를 찾아 봤다.

Github를 의 커밋 로그를 보면 attributesToString 메소드에 $escapeBound 옵션이 추가되었고, sanitizeComponentAttribute란 static 메소드가 추가되었다.

sanitizeComponentAttribute 메소드는 입력된 값이 문자열이거나, object 인경우 __toString 매직 메소드가 존재하면 e()(laravel의 html 이스케이프 함수)로 string을 소독(?) 하는 메소드이다.

근데 x-blade 문법에서 attributes->merge하는 메소드에서는 해당 코드가 적용되지 않아 tag 부분을 이스케이프 할 수 있어 XSS 공격이 가능해진다.

XSS code

<!-- resources/views/welcome.blade.php -->
<x-xss></x-xss>
<!-- resources/views/components/xss.blade.php -->
<div {{ $attributes->merge(['class' => request()->css]) }}>XSS able</div>

URI : http://127.0.0.1:8000/?css="><script>alert(1)</script>

<!-- Result -->
<div class="\"><script>alert(1)</script>">XSS able</div>
XSS in laravel 7.1.2

위와 같이 XSS가 실행되는 것을 볼 수 있다.

Patch

메일 보내고 1시간 만에 패치하고 릴리즈 되었다 ㄷㄷ;

7.1.3 버전에서 패치되었으며[commit] composer update를 하면 자동으로 패치되어진다.

나도 업데이트에서 언급 해줫으면 좋겟다. 나도 찾은건 찾은건데… ㅜ

[PHP] Namespace의 범위

TL;DR

PHP의 Namespace의 범위는 include, require의 상관없이 해당 파일에서만 영향을 끼친다.

Namespace

PHP 5.3부터 추가된 같은 이름의 함수, 클래스가 있을 때 네임스페이스로 각각 격리하여 사용할 수 있게 해주는 기능이다.

Simple example

spaceA.php

<?php

namespace A;

echo __FILE__ . ' > ' . __NAMESPACE__ . PHP_EOL;

function atest() { echo "spaceA atest function\n"; }

spaceB.php

<?php

echo __FILE__ . ' > ' . __NAMESPACE__ . PHP_EOL;

function btest() { echo "spaceB btest function\n"; }

index.php

<?php

namespace INDEX;

require('spaceA.php');
require('spaceB.php');

echo __FILE__ . ' > ' . __NAMESPACE__ . PHP_EOL;

btest();
atest();

php index.php로 php를 실행시키면 결과는 다음과 같다.

/home/silnex/test/spaceA.php > A
/home/silnex/test/spaceB.php > 
/home/silnex/test/index.php > INDEX
spaceB btest function
PHP Fatal error:  Uncaught Error: Call to undefined function INDEX\atest() in /home/silnex/test/index.php:11
Stack trace:
#0 {main}
  thrown in /home/silnex/test/index.php on line 11

require('spaceA.php') 의 결과가 출력된 1번째 줄을 보면
네임스페이스는 index.php에서 선언된 INDEX가 아닌 spaceA.php 파일에서 선언된 네임 스페이스다.

require('spaceB.php')의 결과가 출력된 2번째 줄을 보면
네임스페이스는 INDEX도 A도 아닌 아무 선언 안된 것을 볼 수 있다.
즉, 네임스페이스는 다른파일엔 영향을 끼치지 않는다는 걸 알 수 있다.

btest()의 실행 결과인 4번째 줄을 보면,
정상적으로 실행되고 있으나,
atest()의 경우 5번째줄 부터 표시된 에러가 나는것을 볼 수 있다.

이를 통해 함수가 받는 영향을 알 수 있는데,
기본적으로 Namespace가 없는 함수를 먼저 호출 하고, 만약 함수가 없다면,
현재 파일의 Namespace의 함수를 찾는 것을 확인 할 수 있다.

Laravel 6 to 7 Upgrade

3월 3일 laravel 7이 정식 릴리즈 되면서 기대하던 blade-x, stub, custom casts 등과 같은 기능들이 추가되었다.

지금 열심히 modern php 멤버 분들께서 도큐먼트를 번역중이시니 함께 번역을 하는것은 어떨가 싶다.

이번 글에선 Laravel 6에서 7으로 업데이트할 때 수정되야 하는 사항들을 간략히 정리해보겠다.

composer.json

  1. “php” 의 버전을  “^7.2.5”,
  2. “laravel/framework” 의 버전을 “^7.0″으로
  3. “nunomaduro/collision” 의 버전을 “^4.1″로
  4. “phpunit/phpunit” 의 버전을 “^8.5″로
  5. “facade/ignition” 의 버전을 “^2.0″으로

app/Exceptions/Handler.php

위에서 부터 순서대로
# 6.x
7.x

변경 사항이다.

<?php
# use Exception;
use Throwable;
// ...
# public function report(Exception $exception)
public function report(Throwable $exception)
// ...
# public function render($request, Exception $exception)
public function render($request, Throwable $exception)
// ...

위와같이 composer.json과 Handler.php 파일을 수정했다면
compsoer update를 통해 laravel 7으로 넘어가도록 하자!

[Laravel 7] Zttp

원문: laravel-news

Zttp is coming to Laravel 7

Adam Wathan가 만든 Zttp(Guzzle wapper) 패키지는 Laravel 7에서 새로운 Http 패키지로 선보여 집니다.

이 기능을 추가하기위한 pull request를 보면 Guzzle를 사용할 때보다 약 90%정도 더 좋은 구문을 제공합니다.

새로운 Http에 기본적인 사용 방법에 대한 예시입니다.

use Illuminate\Support\Facades\Http;

$response = Http::post('url', [
    'name' => 'Taylor',
]);

echo $response['foo'];

$response->body()
$response->json()
$response->status()
$response->ok()
$response->successful() (>= 200 && < 300)
$response->serverError()
$response->clientError()

Taylor의 PR내용을 보면, 이 패키지는 완전 새로운 클라이언트가 아닌 Guzzle 윗 레이어에서 UX/DX를 좀더 펼리하게 해주는 역할을 한다고 합니다.

이 패키지는 복잡하고 많은 내용이 추가되는것이 아닙니다. 그리고 만약 더 많은 것들이 필요하다면 Guzzle를 직접 사용할 수 있습니다.

새로운 HTTP 패키지에 대한 내용을 알고 싶으면 pull request를 확인해보세요.


새로운 Http 패키지 분석 (?)

Taylor의 PR를 보면 “Guzzle의 모든 기능을 이 API를 통해 제공하려는게 아니다.”라고 적혀있는걸 보면 말 그대로 TEST나 간단한 Http request정도만 지원하고 나머지는 Guzzle를 직접 적으로 사용하라는것같다.

더 많은 예시

새로운 Http 패키지의 기본적인 content-typeapplication/json이라고 한다.
하지만 아래와 같은 코드로 쉽게 form-urlencoded요청을 보낼 수 있다.

$response = Http::asForm()->post('url', [
    'name' => 'Taylor',
]);

그리고 multi-part타입으로 파일을 보낼 수도 있다.

$response = Http::attach('name', 'stream/contents', 'filename.txt')->post('url');

해더 / 인증정보 (Authentication)

해더는 withHeaders 메소드로 보낼 수 있다.

$response = Http::withHeaders(['X-Foo' => 'bar'])->post('url', [
    'name' => 'Taylor',
]);

인증 / bearer 과 같은 토큰 정보는 withToken메소드로 보낼 수 있다.

$response = Http::withToken('token')->post('url', [
    'name' => 'Taylor',
]);

Basic Authenication은 withBasicAuth메소드로 보낼 수 있다.

$response = Http::withBasicAuth('username', 'password')->post('url', [
    'name' => 'Taylor',
]);

에러

ZTTP와 같이 기본적으로 서버, 클라이언트 에러를 출력하지 않는다고 한다.
하지만 만약 HTTP응답이 실패했을때 $response->throw()를 통해 예외 처리를 할 수 있다.

$response = Http::post(...);

if (! $response->successful()) {
    $response->throw();
}

Testing / Faking

Http::fake()를 지원하며, 기본적으로 비여있는 200응답을 한다.

Http::fake();

$response = Http::post('url');

Http::fake()는 인자로 Array를 받으며, 패턴이 일치하는 경우는 Http::response를 통해 응답을 만들어 낼 수 있으며, 만약 일치되는 패턴이 없다면, 실제로 실행된다.

Http::fake([
    'github.com/*' => Http::response([1, 2, 3], 200, ['Headers']),
]);

만약 모든 패턴에 대해서 실행되는것을 막고 싶다면 아래와 같이 작성할 수 있다.

Http::fake([
    'github.com/*' => Http::response([1, 2, 3], 200, ['Headers']),
    '*' => Http::response('', 200),
]);

연속적인 응답은 Http::sequentce를 이용해 지정된 순서에 맞게 응답이 출력된다.

Http::fake([
    '*' => Http::sequence([Http::response('foo', 200), Http::response('bar', 200)]),
]);

마지막으로 Http::fake()는 클로저를 지원한다.

Http::fake(function ($request) {
    return Http::response('foo', 200);
});

잡-설

잘 모르는 영어와 개발 코드를 보니 복잡하네요..
새로운 패키지가 들어오는건 좋지만 아직도 배울게 한참많은데
배워야 될것들이 계속 늘어나는걸 보면,, ㅜㅜ

[Laravel 7] Custom mutators

TL; TR

Laravel 7.x에선 모델의 Property인 $casts에 커스텀된 클래스를 전달해줄 수 있다.[링크]

기존의 Mutators

Laravel 7.0의 공개 일자는 2020년 2월로 현재 얼마 남지 않은 상황에 laravel.com/doc/master에 차근차근 새로운 기능들에 대한 문서들도 업데이트 되는 것 같다.

이번에 getVarNameAttribute()setVarNameAttribute($value) 를 사용하면서 mutator에 대한 커스텀은 안될까 하여 찾아보다가 master문서에 custom mutators 항목이 추가된 것을 발견하고 곧 7.0이 릴리즈 될테니 테스트겸 시도해보았다.

Laravel 7 dev 버전에 대한 설치는
laravel new laravel7 --dev 또는
composer create-project --prefer-dist laravel/laravel:7.x-dev laravel7 으로 설치 할 수 있다.

$ cd laravel7
$ php artisan --version
Laravel Framework 7.x-dev

위와같이 artisan 명령어로 버전을 확인할 수 있다.
과거엔 welcome 템플릿에 laravel 7.0 (DEV) 🚀 라고 적혀있엇는데 아마 곧 공개를 앞으도 바뀐것같다.

++ User 테이블로 Custom mutator를 테스트 해보려고 하는데 laravel/ui 가 설치되지 않는다. 찾아보니 laravel7.x 부턴 laravel/ui에 login, register 등의 컨트롤러가 포함되어 배포되기에 laravel/ui 2.x를 설치해 줘야한다.
composer requrie laravel/ui:2.x-dev --dev

기존에 mutators의 경우 array, datetime 등의 일부만 작동을 했고 만약 커스텀하게 짜기위해선 아래와 같이 해당 모델에 get, set~~Attribute 메소드를 짜줘야했다.

// App/User

public function getNameAttribute()
{
    return "my name is {$this->name}";
}

public function setNameAttribute($value)
{
    $this->name = ucfirst($value);
}

하지만 여러 테이블에서 사용될 때 반복되고 모델에도 만약 수정이 필요할 때 모델별로 다 수정해줘야하는 번거로움이 생긴다 (trait를 써도되지만 일단넘어가자..)

이번 Laravel 7.x 에선 Illuminate\Contracts\Database\Eloquent\CastsAttributes 클래스를 사용해서 datetime, array와 같인 $casts 프로퍼티에 지정할수있다.

App\Casts\Json

<?php

namespace App\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class Json implements CastsAttributes
{
    /**
     * Cast the given value.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  mixed  $value
     * @param  array  $attributes
     * @return array
     */
    public function get($model, $key, $value, $attributes)
    {
        return json_decode($value, true);
    }

    /**
     * Prepare the given value for storage.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  array  $value
     * @param  array  $attributes
     * @return string
     */
    public function set($model, $key, $value, $attributes)
    {
        return json_encode($value);
    }
}

App\User

<?php

namespace App;

use App\Casts\Json;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'options' => Json::class,
    ];
}

위와 같이 CastsAttributes 클래스를 받고 해당 클래스를 $casts로 전달해주면 자동으로 맵핑해준다.

지금은 casts를 따로 생성하는 artisan명령어는 없는것같지만, laravel 7이 릴리즈되면 추가되지 않을까싶다.

PHPUnit에서 Laravel 에러 메시지 출력 패키지(Collision) 사용

원글 Collision PHPUnit Listener With Laravel

Collision‘는 Laravel에서 아래 이미지처럼 상세한 오류 내역을 출력해주는 패키지이다.

View image on Twitter
[사진] Nuno maduro 트위터

Laravel로 개발하면서 정말 좋은 기능임을 알고있었지만, phpunit과 같은 외부 테스트 도구들을 통해 테스트할 때 오류가 발생해도 아래 이미치 처럼 단순히 Exception stack만 보여줘 ‘Collision’ 처럼 자세한 내용을 제공 받지 못해 아쉬웠던 적이 더러 있었다.

하지만 작년 12월 31일 Collision 패키지 개발자가 트위터를 통해 PHPUnit adapter를 언급하면서 간단한 설정만으로 phpunit에서도 Collision 화면을 볼 수 있게 되었다.

기존 phpunit은 아래 이미지처럼 Exception이 일어나면 단순히 Exception stack만을 표시 했지만,

[laravel-new] phpunit 에러 메시지

phpunit.xml파일에 Collision 리스너를 추가한다.

<listeners>
    <listener class="NunoMaduro\Collision\Adapters\Phpunit\Listener" />
</listeners>
[laravel-new] Listener 추가 후 phpunit 에러 메시지

그러면 위와 같이 phpnuit에서도 깔끔하게 출력되며 좀 더 나은 디버깅 환경을 제공해준다.

[PHP] 폴더 내에 모든 파일을 autoload하는 방법

라라벨에서 custom helper 함수를 추가할 땐 “app/helpers/helper.php”와 같은 파일을 만들고 아래 json과 같이 그 파일을 composer.json에 “files”에 추가해주어야 한다.

"autoload": {
    "files": [
        "app/helper/helpers.php"
    ],
    "classmap": [
        "database/seeds",
        "database/factories"
    ],
    "psr-4": {
        "App\\": "app/"
    }
},
"autoload-dev": {
    "psr-4": {
        "Tests\\": "tests/"
    }
},

다만 하나의 file을 추가할 때는 괜찮아 보이지만 만약 추가해야하는 custom helper가 많아질 수록 관리도 힘들고 일일히 추가해야 하는 번거로움도 있다.

이럴때 폴더 단위로 app/Helpers/* 와 같이 로드되면 좋겠지만 composer의 files는 단일 파일만을 지원하며 * 와같이 여러 파일을 불러오는 기능은 존재하지 않는다.
(다만 class 와 관련되어선 사용할 수 있는것같다, 아마 namespace를 이용하지 않을까…?)

이럴때 app/helper/include.php 파일을 생성한 뒤 아래와 같이 작성해 준 뒤,

<?php
$files = glob(__DIR__ . '/*.php');
if ($files === false) {
    throw new RuntimeException("Failed to glob for function files");
}
foreach ($files as $file) {
    require_once $file;
}
unset($file);
unset($files);

include.php 파일을 composer.json에 추가해준다.

"autoload": {
    "files": [
        "app/helper/include.php"
    ],
    "classmap": [
        "database/seeds",
        "database/factories"
    ],
    "psr-4": {
        "App\\": "app/"
    }
},
"autoload-dev": {
    "psr-4": {
        "Tests\\": "tests/"
    }
},

이후 composer dump-autoload 명령어를 통해 로드 해주면 app/helpers 폴더 안에 있는 모든 php 파일들에서 선언된 함수, 클래스들을 사용할 수 있다.

[VSCode] NPM Rename Permission Error

NPM Rename permission error

VScode에서 WSL Remote를 지원하면서 NPM을 이용한 패키지 관리도 VSCode 상에서 진행을 하는중 https://github.com/Microsoft/WSL/issues/2097 이슈처럼 계속 Rename 권한 이슈때문에 npm install이 진행되지 않는다..

해결 방법은 단순한데 VSCode를 끄고 npm install을 진행하면 된다.
VSCode가 실행되는 중에 npm install을 사용하면 VSCode의 폴더를 캐싱하는 부분과 겹쳐지면서 파일 쓰기가 되지 않아 생기는 문제로 보여진다.

이 문제로 고생하는 사람이 꾀나 있는데 이 글이 도움이 되었으면 좋겠다.