サーバ上のエラー発生行をブラウザからPhpStormで開く方法

こんにちは岡野です。
今回はPHPでの開発が楽になる方法を紹介します。最初の設定は面倒ですがメリットは大きいため、PhpStorm(もしくはIntelliJのPHPプラグイン)を使っている方はぜひ試してください。
   
  • 環境
    Windows
    IntelliJ IDEA 2023.2 + PHP Plugin
    Symfony(おそらく他のフレームワークでも対応可能)
    PhpStorm Protocol (最終編集2023-08-17)
  • インストール
  1. https://github.com/aik099/PhpStormProtocol のCodeボタンからzipをダウンロードする。  
  2. zip内の「PhpStorm Protocol (Win)」 を C:\Program Files\ へコピーする。  
  3. C:\Program Files\PhpStorm Protocol (Win)\run_editor.reg をダブルクリック。OK押下。  
  4. run_editor.jsを編集。上記PhpStormProtocol説明ページに載っている方法でうまくいかない場合は、editor変数を直接変更する。  
     
    editor = ‘”C:\\Users\\ユーザ名\\AppData\\Local\\Programs\\IntelliJ IDEA Ultimate\\bin\\idea64.exe”‘;
     
  5. Symfonyの設定を行う。 例は、サーバ:/opt/app/、ローカル:c:\git\app\symfony/というマッピングの場合。
(Symfony6.1以上の場合)
.env.localに以下を記述。
SYMFONY_IDE=phpstorm://open?file=%f&line=%l&/opt/app/>c:/git/app/symfony/
 
(Symfony6.0以下の場合)
framework.yaml内のframeworkにide要素を追加。
framework:
  ide: 'phpstorm://open?file=%%f&line=%%l&/opt/app/>c:/git/app/symfony/'
 
・実行
エラー発生時にファイルパスのリンクをクリックすると、該当箇所がIntelliJで開く。

ImageMagick脆弱性対応(パッチ)

こんにちは岡野です。
先日行った、画像処理ライブラリImageMagickの脆弱性対応を共有します。当社ではオリジナルプリントやMEET MY GOODSといったサービスで処理高速化のためリコンパイルしたImageMagickを使用しています。

脆弱性の概要

脆弱性(Metabase Qの報告
CVE-2022-44267:DoS攻撃
CVE-2022-44268:任意のファイル参照
ですが、不正なテキスト情報を埋め込んだPNGファイルをImageMagickが処理する際に発生する物でした。

脆弱性の対応案

・ImageMagickのバージョンアップ
ImageMagickを最新版へバージョンアップすれば解決するとのことでしたが、テストに時間が掛かるため他の方法を検討しました。

・画像データの修正
脆弱性の原因は前述の通り不正なテキスト情報ですので、ImageMagickへ画像ファイルを渡す前に不正なテキスト情報を除去する案です。PNG最適化ツールpngcrushで除去する方法をネットで見つけ試してみましたが除去処理に時間が掛かるためあきらめました(バージョンにより違うのかもしれませんが大きめの画像では10秒以上掛かることがありました)。

・ImageMagickのパッチ適用
脆弱性を修正するパッチをImageMagickのソースコードへ適用する案です。今回はこの案で対応しました。

パッチ適用の手順

今回はUbuntu用のパッチを流用しました。
https://launchpad.net/ubuntu/+source/imagemagick/8:6.9.7.4+dfsg-16ubuntu6.15
の「Available diffs」の箇所です。
但しとあるツイートに存在したPNGファイル
echo -en "P4\n1 1\nX" | pnmtopng -text <(echo 'fx:while(1,debug(hui=3)) 0') > payload.png
の脆弱性を防げなかったためパッチのif文
if ((LocaleCompare(key,"version") == 0) ||
    (LocaleCompare(key,"profile") == 0) ||
    (LocaleCompare(key,"width") == 0))
へ「fx:」を無効化する
    (LocaleNCompare(key,"fx:",3) == 0) ||
を追加しました(ImageMagickのバージョンによっては再現しなさそうでした、おそらくこのissue)。

まとめ

残念ながらImageMagickの脆弱性は今後も発見されると思いますので、バージョンアップを容易にしておく必要性を感じました。今後はバージョンアップ用の自動テストケースを準備しようと思います。

ImageMagickで服の色を変える

こんにちは岡野です。
現在開発中のプロジェクトで、服の色を変更する処理を実装したので紹介します(簡易的な方法です)。

環境

ImageMagick 6.9.12-59 Q8

手順

1. 元となる画像を用意します。例:Tシャツ ホワイト。
2. 例えばネイビーへ変更するとして変更前後の色についてRGBそれぞれの差分を計算します。
ホワイト: rgb(255, 255, 255)
ネイビー: rgb(32, 47, 85)
R: 32 - 255 = -223
G: 47 - 255 = -208
B: 85 - 255 = -170
3. ImageMagickのevaluate Addに差分の値を渡して色を変換します(Q16環境では各値を256倍します)。
convert src.png \
-channel R -evaluate Add -223 \
-channel G -evaluate Add -208 \
-channel B -evaluate Add -170 \
dest.png

まとめ

ImageMagickだけでも結構良い感じになりました。将来はAIで着色したいと思います。

その他

最近Zabbixでハマったことです。グラフ内の文字が出力されないため悩んでいたのですがopen_basedirの設定不足でした(zabbix_server.logに気をとられNginxのログに気づくのが遅れた)。何か問題があった際は落ち着かないとダメだと感じました。

Symfonyで2要素認証

こんにちは、岡野です。
最近Symfonyを使っているWebサイトで2要素認証を実現しました。その際に使用したscheb/2faバンドルは非常に使いやすく、Symfony公式マニュアルを見ながら容易に実現できました。但し一点問題があり、認証コードを入力しないままユーザが画面遷移すると中途半端な認証状態になってしまうので困りました。
今回はその回避方法を共有します。
 

環境

Symfony: 4.4.20
scheb/2fa-*: 5.13.1
 

起きていた現象

1. ユーザがID/PWを入力。
2. 認証コードを尋ねるポップアップを表示。
3. ユーザが他の画面へ遷移。

→ 2.でSymfony内部の認証状態が「Partially authenticated」になるため、3.でController#getUser()すると認証が完了していないユーザエンティティが取得できてしまう(※1)。
また3.のページがfirewallで制限している場合は認証コードを尋ねるページが表示される。一般的な通常サイトの様にログイン画面へ遷移させたい(※2)。
 

※1(完全認証前のユーザが取得できてしまう)の回避方法

EventListenerを使って、認証コードの確認が完了するまでuserをanonymousにする。
 
class AuthAttemptListener implements EventSubscriberInterface
{
  public static function getSubscribedEvents(): array
  {
      return [
          TwoFactorAuthenticationEvents::ATTEMPT => ['onTwoFactorAuthAttempt'],
          TwoFactorAuthenticationEvents::COMPLETE => ['onTwoFactorAuthComplete'],
          TwoFactorAuthenticationEvents::FAILURE => ['onTwoFactorAuthFailure'],
          KernelEvents::RESPONSE => ['onKernelResponse'],
          KernelEvents::TERMINATE => ['onKernelTerminate'],
      ];
    }

  /**
   * セッション内の情報が正しければ、バンドル内の2要素認証処理用にトークンへユーザを設定する。
* ユーザは後で解除する。
   */
  public function onTwoFactorAuthAttempt(TwoFactorAuthenticationEvent $event): void
  {
      // ...
      $this->attemptingToken->setUser($user);
    }

  /**
   * 2要素認証が成功した場合、トークンを認証状態に設定する。
   */
  public function onTwoFactorAuthComplete(TwoFactorAuthenticationEvent $event): void
  {
      // ...
      $this->attemptingToken->getAuthenticatedToken()->setAuthenticated(true);
    }

  /**
   * 2要素認証失敗時、トークンのユーザを元に戻す
   */
  public function onTwoFactorAuthFailure(TwoFactorAuthenticationEvent $event): void
  {
      if ($this->attemptingToken) {
          $this->revertTokenUser();
      }
    }

  /**
   * 例外発生時、トークンのユーザを元に戻す
   */
  public function onKernelResponse(): void
  {
      if ($this->attemptingToken) {
          $this->revertTokenUser();
      }
    }

  /**
   * 安全のためSymfonyの処理が終わる際に、万が一処理中のトークンが残っていたら
* 例外を発生させてエラー通知する。
   */
  public function onKernelTerminate(): void
  {
      if ($this->attemptingToken) {
          throw new \LogicException();
      }
    }

  private function revertTokenUser(): void
  {
      $this->attemptingToken->setUser('anon.');
      $this->attemptingToken->getAuthenticatedToken()->setAuthenticated(false);
      $this->attemptingToken = null;
  }
}

※2(認証コード要求ページが表示される)の回避方法

認証コードが要求された場合、ログイン画面へ遷移する。
 
services.yaml
  app_2fa_required_handler:
        class: App\Security\TwoFactorAuth\AuthRequiredHandler

class AuthRequiredHandler implements AuthenticationRequiredHandlerInterface
{
  public function onAuthenticationRequired(Request $request, TokenInterface $token): Response
  {
      $this->tokenStorage->setToken(new AnonymousToken('', 'anon.'));
      throw new AccessDeniedException();
  }
}
参考になれば幸いです。

Doctrine SQL Filterでマルチテナントを実現する

こんにちは、岡野です。

最近マルチテナントのシステムを開発することがあり、共通のDBをテナントごとに管理する方法を調べました。

結論

Doctrineの以下機能を使用するとCRUDの各SQLを改変でき、テナントを透過的に制御可能(すなわち毎回WHERE句にテナントの条件を書かなくて良い)。 但し、@OneToOneは期待通りに動作しないため要注意。

環境

Doctrine 2.7.4
Symfony 4.4.16

SELECTの調査結果

以下の様なSQL Filterを登録。
class TenantFilter extends SQLFilter
{
    public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
    {
        // 100は実際には可変(後述)
        return $targetTableAlias.'.tenant_id = 100';
    }
}

単純なSELECT

$em->find(Parent::class, 1);
→ SELECT ... FROM parent WHERE id = 1 AND tenant_id = 100;

@OneToMany

$em->find(Parent::class, 1)->getChilds();
→ SELECT ... FROM parent WHERE id = 1 AND tenant_id = 100;
→ SELECT ... FROM child WHERE parent_id = 1 AND tenant_id = 100;

@ManyToOne

@OneToMany同様。

@ManyToMany

@OneToMany同様。

@OneToOne

$em->find(Example1::class, 1)->getExample2();
→ SELECT ... FROM example1 e1 LEFT JOIN example2 e2 ON e2.example1_id = e1.id WHERE e1.id = 1 AND e1.tenant_id = 100;
⇒ example2テーブルへのtenant_id = 100が存在しない

join()

$em->createQueryBuilder('c')->join('c.parent')->where('c.id = 1');
→ SELECT ... FROM child c JOIN parent p ON c.parent_id = p.id AND p.tenant_id = 100 WHERE c.id = 1 AND c.tenant_id = 100;

leftJoin()

join()同様。



INSERT/UPDATE/DELETEの調査結果

以下の様なDoctrine Event Listenerを登録。
class TenantListener
{
    public function prePersist(LifecycleEventArgs $args): void
    {
        assert($args->getEntity()->getTenant() === null);
        // 100は実際には可変
        $args->getEntity()->setTenant($this-em->find(Tenant::class, 100));
    }
    public function preUpdate(LifecycleEventArgs $args): void
    {
        // 安全のため他テナントを操作していないかチェックする
        if ($args->getEntity()->getTenant()->getId() !== 100) {
            throw new \LogicException();
        }
    }
    public function preRemove(LifecycleEventArgs $args): void
    {
        // preUpdate()同様
    }
}

INSERT

$em->persist(new Example());
→ INSERT INTO example (..., tenant_id) VALUES (..., 100);


その他

上記のようにDoctrine FilterのaddFilterConstraint()内でtenant_idを直接設定するとDoctrine Query CacheによりSQLが固定化されてしまい期待通りに動作しない。そのため以下の通りparameterを経由する必要があった。

// 外部から呼ぶ(RequestListenerなどから)
public function setTenantId(int $tenantId): void
{
    $this->setParameter('tenant_id', $tenantId, Types::INTEGER);
}

public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
{
    $tenantId = (int)$this->unquoteParameterValue($this->getParameter('tenant_id'));
    return $targetTableAlias.'.tenant_id = '.$tenantId;
}

private function unquoteParameterValue(string $value): string
{
    $len = strlen($value);
    if ($value[0] !== "'" || $value[$len - 1] !== "'") {
        throw new \LogicException($value);
    }
    return str_replace(['\\"', '\\\\'], ['"', '\\'], substr($value, 1, $len - 2));
}

RectorでSymfony4への変換

こんにちは、岡野です。

最近、Symfony2.8/PHP7.0で実装されたサービスをSymfony4.4/PHP7.4へバージョンアップしました。その際ソースコードの自動変換に使用したRectorというソフトウェアを紹介します。
詳しくは https://getrector.org/ を見ていただくとし、実際の変換結果を挙げていきます。    

変換結果

Symfony2.8 -> 2.8

元々Symfony2.8で実装していますが念のためRectorを実行します。

最初に設定ファイルを準備します。
$ vi rector.php
...
$parameters->set(Option::PATHS, [
    __DIR__.'/app',
    __DIR__.'/src',
]);
$parameters->set(Option::EXCLUDE_PATHS, [
    __DIR__.'/app/SymfonyRequirements.php',
    __DIR__.'/app/cache/',
    __DIR__.'/src/AppBundle/Tests/',
]);
$parameters->set(Option::PHP_VERSION_FEATURES, '7.0'); // 適宜変更
$parameters->set(
    Option::SYMFONY_CONTAINER_XML_PATH_PARAMETER,
    __DIR__.'/appDevDebugProjectContainer.xml'
);
そしてRectorを実行します。
$ vendor/bin/rector process --set symfony28
以下変換されました(以降、変換結果の一部を抜粋します)。
/**
 * @Route(defaults={"foo": ""}, ...)
 */
↓ ↓ ↓
/**
 * @Route(defaults={"foo"= ""}, ...)
 */
 

Symfony2.8 -> 3.0

$ vendor/bin/rector process --set symfony30
$form = $this->createForm(new Foo());
↓ ↓ ↓
$form = $this->createForm(\AppBundle\Form\Foo::class);
 

Symfony3.0 -> 3.4

(symfony31~symfony33は差分が発生しませんでした)
$ vendor/bin/rector process --set symfony34
/**
 * @Route(...)
 * @Method("POST")
 */
↓ ↓ ↓
/**
 * @Route(..., methods={"POST"})
 */
 

PHP7.0 -> 7.0

$ vendor/bin/rector process --set php70
isset($foo[$bar]) ? $foo[$bar]: 0
↓ ↓ ↓
$foo[$bar] ?? 0

rand();
↓ ↓ ↓
random_int(0, mt_getrandmax());
 

PHP7.0 -> 7.1

$ vendor/bin/rector process --set php71
list($a, $b) = $this->foo();
↓ ↓ ↓
[$a, $b] = $this->foo();

count($foo)
↓ ↓ ↓
is_array($foo) || $foo instanceof \Countable ? count($foo) : 0 // php73実行時に改善される
 

Symfony3.4 -> 4.2

(symfony40,symfony41は差分が発生しませんでした)
$ vendor/bin/rector process --set symfony42
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class FooController extends Controller
↓ ↓ ↓
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class FooController extends AbstractController
 

PHP7.1 -> 7.3

(php72は差分が発生しませんでした)
$ vendor/bin/rector process --set php73
is_array($foo) || $foo instanceof \Countable ? count($foo) : 0
↓ ↓ ↓
is_countable($foo) ? count($foo) : 0

json_encode($foo)
↓ ↓ ↓
json_encode($foo, JSON_THROW_ON_ERROR)

json_decode($foo)
↓ ↓ ↓
json_decode($foo, false, 512, JSON_THROW_ON_ERROR)
 

Symfony4.2 -> 4.3

$ vendor/bin/rector process --set symfony43
$event->getDispatcher()->dispatch(
    FOSUserEvents::SECURITY_IMPLICIT_LOGIN,
    new UserEvent($event->getUser(), $event->getRequest()));
↓ ↓ ↓
$event->getDispatcher()->dispatch(
    new UserEvent($event->getUser(), $event->getRequest()),
    FOSUserEvents::SECURITY_IMPLICIT_LOGIN);

use Symfony\Component\HttpKernel\Event\GetResponseEvent;
public function foo(GetResponseEvent $event)
↓ ↓ ↓
use Symfony\Component\HttpKernel\Event\RequestEvent;
public function foo(RequestEvent $event)
 

Symfony4.3 -> 4.4

$ vendor/bin/rector process --set symfony44
// Command class
public final function execute(InputInterface $input, OutputInterface $output)
↓ ↓ ↓
public final function execute(InputInterface $input, OutputInterface $output): int
 

PHP7.3 -> 7.4

$ vendor/bin/rector process --set php74
/**
 * @var integer
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
private $id;
↓ ↓ ↓
/**
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
private int $id;

/**
 * @var integer
 * @ORM\Column(name="foo", type="integer", nullable=true)
 */
private $foo;
↓ ↓ ↓
/**
 * @ORM\Column(name="foo", type="integer", nullable=true)
 */
private ?int $foo = null;

/** @var EntityManager */
private $em;
↓ ↓ ↓
private \Doctrine\ORM\EntityManager $em; // use形式への変更は手作業が必要

1024
↓ ↓ ↓
1_024 // この変換は微妙
 

Twig1.x -> 2.0

$ vendor/bin/rector process --set twig20
new \Twig_SimpleFilter(...)
↓ ↓ ↓
new \Twig_Filter(...)
 

Twig2.0 -> 2.4.0

$ vendor/bin/rector process --set twig240
new \Twig_Filter(...)
↓ ↓ ↓
new \Twig\TwigFilter(...)
 

まとめ

正規表現による置換よりもソフトウェアで一括変換した方が抜けも誤りも少ないでしょう。

だし誤変換もごく一部あります。未定義の配列へ要素追加しているコードが $foo = (array)$foo; $foo[] = $bar; と変換されるなどです(元々未定義なことが良くないのですが)。

って変換結果にざっと目を通すことは必要ですが、ほとんどはPHP/Symfony文法エラーのため容易に気付けます。変換後にPHPStormのInspect Code機能で確認することも有効でした。

当然
ながらRectorで変換できない点もありますので手動対応は必要です(src/AppBundle -> src/など)。

その他

­ 上記以外にもRectorのルールセットは多数ありますので興味ありましたら以下をご覧ください。
https://github.com/rectorphp/rector/tree/master/config/set

LinuxサーバをVPN(L2TP)クライアントにする

こんにちは、岡野です。

先日、さくらインターネットのVPSと社内ネットワーク(VPN構築済み)をL2TPで接続しました。 特に問題なく接続できたのですが手順を共有します。

前提条件

VPN: L2TP

VPS: Ubuntu 20.04、ufwで通信制限中(incoming & outgoing)

接続手順

  1. VPNサーバの対応アルゴリズムを調べる

NetworkManager-l2tp Wikiにあるシェルスクリプトを保存後、以下を実行。VPNサーバの対応アルゴリズムが表示される。

$ chmod +x ike-scan.sh
$ sudo ./ike-scan.sh <VPNサーバのIP> | grep SA=
抜粋
SA=(Enc=AES Hash=SHA1 Auth=PSK Group=2:modp1024 KeyLength=256 LifeType=Seconds LifeDuration(4)=0x00007080)
  1. VPSのネットワーク設定を確認する

さくらインターネットで設定済みのネットワーク設定を確認。あとで使用する。

$ cat /etc/netplan/01-netcfg.yaml
network:
version: 2
renderer: networkd
ethernets:
  ens3:
    addresses: [***.***.***.***/23]
    gateway4: ***.***.***.***
    nameservers:
      addresses: [***.***.***.***, ***.***.***.***]
  1. L2TPソフトウェアをインストールする

$ sudo apt install network-manager-l2tp
  1. NetworkManager管理に切り替える

$ sudo vi /etc/netplan/01-netcfg.yaml
network:
version: 2
renderer: NetworkManager
# 残りは削除
$ sudo reboot
  1. NetworkManagerの接続情報を設定する

以下、VPSコンソールから実施。

$ sudo nmtui
# 「2. VPSのネットワーク設定を確認する」の内容を設定する
$ sudo reboot
  1. VPNの接続情報を設定する

VPN接続情報を以下の通り設定する。IKE対応アルゴリズムには「1. VPNの対応アルゴリズムを調べる」で調べた中で、より強力なものを使用する(例:aes256-sha1-modp1024)。

$ sudo nmcli connection add \
type vpn \
con-name <接続名、適当に。例:vpn> \
autoconnect no \
ifname -- \
ipv4.method auto \
vpn-type l2tp \
vpn.secrets password=<パスワード> \
vpn.data \
"password-flags = 0, require-mppe = yes, user = <ユーザ名>, refuse-chap = yes, refuse-mschap = yes, gateway = <VPNサーバのIP>, refuse-pap = yes, ipsec-enabled = yes, ipsec-psk = <共有鍵>, ipsec-ike=<IKE対応アルゴリズム>, ipsec-esp=aes128-sha1"
  1. VPN接続する

ログを表示しながらVPN接続を試みる。

$ journalctl -f &
$ sudo nmcli conn up vpn

以下の様なエラーが出力されるため

Jul 19 09:04:16 *** kernel: [148247.538797] [UFW BLOCK] IN= OUT=ens3 SRC=***.***.***.*** DST=***.***.***.*** LEN=268 TOS=0x00 PREC=0x00 TTL=64 ID=13477 DF PROTO=UDP SPT=500 DPT=500 LEN=248

ufwで解除設定しながらsudo nmcli conn up vpnを繰り返す。具体的には以下の様なufw設定になる。

$ sudo ufw status
***.***.***.*** 500/udp     ALLOW OUT   Anywhere                   # vpn (ipsec)
***.***.***.*** 1701/udp   ALLOW OUT   Anywhere                   # vpn (l2tp)
***.***.***.***/esp         ALLOW OUT   Anywhere                   # vpn (esp)

必要に応じてルーティング設定も行う。

  1. VPN切断する

VPN切断は以下。

$ sudo nmcli conn down vpn

Symfony Form Type Options Guessing

こんにちは、岡野です。
Symfonyで 開発中のプロジェクトでvalidateのコードを減らしたく、Form Type Options Guessingを色々と試した結果を共有します( Symfony4.3 )。
参考:https://symfony.com/doc/4.3/forms.html#form-type-options-guessing
1.既存のコード
FormBuilder記述
  add('sort', TextType::class)
Entity記述
  @ORM\Column(type="integer")
生成HTML
  <input type="text" id="..." name="..." required="required">
  (以降、id/name/required属性は省略)
type=”text”の場合、EntityでsetSort(?int)などと型指定していると、数値でない”a”などをsubmitした際にPropertyAccessorでInvalidArgumentExceptionが発生してしまう。
2.クラスを省略
FormBuilder
  add('sort')
Entity
  @ORM\Column(type="integer")
HTML
  <input type="number">
type=”number”になった。
3.Rangeアノテーションを追加
FormBuilder
  add('sort')
Entity
  @ORM\Column(type="integer")
  @Assert\Range(min="0", max="99999")
HTML
  <input type="number" maxlength="5" pattern=".{1,}">
maxlength/pattern属性は増えたが、min, max属性は付けてくれない。 type=”number”の場合maxlength属性が効かない様で、123456の様な値でsubmitできてしまう(以降Chrome78で確認)。
4.add()の引数でmin/maxを指定
FormBuilder
  add('sort', null, ['attr' => ['min' => 0, 'max' => 99999]])
Entity
  @ORM\Column(type="integer")
  @Assert\Range(min="0", max="99999")
HTML
  <input type="number" maxlength="5" pattern=".{1,}" min="0" max="99999">
123456は入力できるがsubmitはできない。
5.add()の引数でpatternも指定
FormBuilder
  add('sort', null, ['attr' => ['min' => 0, 'max' => 99999, 'pattern' => '\d{1,5}']])
Entity
  @ORM\Column(type="integer")
  @Assert\Range(min="0", max="99999")
HTML
  <input type="number" maxlength="5" pattern="\d{1,5}" min="0" max="99999">
type=”number”の場合pattern属性が効かない様で、123456が入力できる。submitはできない。
結局、開発中のプロジェクトでは3.で進めることにしました。

Ubuntuについて2

岡野です。
前々回のブログ「Ubuntuについて」の続きです。
Ubuntuの良い所を書きます。

メリット2 サポート期間の延長が可能

当社にはUbuntu14を使用しているサービスが一部あり、まもなく5年のサポート期間が切れます。 OSをバージョンアップするのは大変なためESM(Extended Security Maintenance)を導入するかもしれません。
ESMとは主要なパッケージについて有料でセキュリティパッチが提供されるサポートサービスです。 但しESMにはUbuntu12での実績によるとImageMagick等がサポート対象に入っていない様です。 そのためESMを導入してもApache等を除く個々のソフトウェアについては個別に対応する必要がありそうです。
なお他のLinuxディストリビューションでは無料サポート期間が5年以上の物も一応あります。

メリット3 情報が見つけやすい

最近ImageMagickの動作速度が遅く感じられることがあり、Meltdown等の対策パッチの影響かと思って色々と調べました。 Ubuntuのパッチについての情報はすぐに見つかりました。
上記は公式サイトの例ですが、stackoverflowやserverfaultなどでもUbuntuの情報は他のLinuxディストリビューションより見つけやすい気がします。
なお少々古い環境ですが実験した所では以下の通り、Meltdown等の対策パッチによるImageMagick動作速度への影響はありませんでした。
・CPU
Xeon E5-2407
・OS
Ubuntu 14.04(パッケージ最新状態)
・カーネル
3.13.0-167-generic #217-Ubuntu SMP Wed Mar 13 16:18:21 UTC 2019 x86_64
・パッチ無効化方法
grubで”nopti nopcid noibrs noibpb nospectre_v2 nospec_store_bypass_disable”を指定。
・コマンド実行
“time convert 7134×7134.png -colorspace rgb -strip png32:test.png”を試したが、パッチ状態に関わらず共に12.6秒。

WordPressのセキュリティ

こんにちは、岡野です。
最近、当社で使用しているWordPressについてセキュリティ向上を図る案件があったので紹介します。

まずOWASPに載っている以下の様な事を実施しました。

・allow_url_fopenの禁止
この設定はファイル名にURLを指定できなくする物です。

・disable_functionsで、コマンド実行系(exec()など)の関数を禁止
この設定はOSコマンドインジェクションの防御には結構有効で、後述のAppArmorと組み合わせるとより効果的です。

・WP Security Audit Logプラグインをインストールし、操作履歴を保存
これは何か問題が生じた際の調査に使用します。

また、あらかじめ指定したPHPファイル以外はWebアクセスできない様にしています。

その他、FPMからDB接続ができなくなるため現在調整中ですがAppArmorも設定予定です。

WordPressもスマホアプリの様にプラグインやテーマ毎に細かな権限設定ができればと思いますが、PHP自体にその様な機能が無いため難しいかもしれません。